Repository: kn007/patch Branch: master Commit: 879caa54f2a2 Files: 29 Total size: 1.2 MB Directory structure: gitextract_z0ab69s5/ ├── CustomHighlightSyntax.msyn ├── DoNotProxy.list ├── DoProxy.list ├── Enable_BoringSSL_OCSP.patch ├── LICENSE ├── README.md ├── blacklist.dgjy ├── bypass.list ├── dns.routes ├── dropbox_fs_fix.patch ├── fcm.hosts ├── ffmpeg-let-rtmp-flv-support-hevc-h265-opus.patch ├── gl_mt1300_led_daemon.patch ├── keys.dict ├── nginx.patch ├── nginx_dynamic_tls_records.patch ├── nginx_for_1.23.4.patch ├── nginx_io_uring.patch ├── nginx_with_quic.patch ├── nginx_with_quic_for_1.19.6.patch ├── nginx_with_quic_for_1.19.7_full.patch ├── nginx_with_spdy.patch ├── nginx_with_spdy_quic.patch ├── openssl-1.1.1.patch ├── rimworld.mods ├── seewo.fw ├── trackers.best.cn.list ├── trackers.list └── use_openssl_md5_sha1.patch ================================================ FILE CONTENTS ================================================ ================================================ FILE: CustomHighlightSyntax.msyn ================================================ [CustomSyntax] Name=Custom UseRegex=1 Underline=[^A-Za-z_&-](http(s)?://[A-Za-z0-9_.&?=%~#{}()@+-]+:?[A-Za-z0-9_./&?=%~#{}()@+-]+)[^A-Za-z0-9_-] Red=[^A-Za-z0-9_\-](loss|down|deny|disabled?|unknown|fault|shutdown|disconnected|error|failed|denied|not permitted|disallowed|not allowed|refused|Attack occurred|problem|failure|not permitted|notconnect|service-type|forbidden-ip|excluded-ip-address|sysname|hostname|arp static|ip ((rpf-)?route(-static)?(-group)?|forward-broadcast)|user-bind static|static-bind|stp( global)?|lacp|reboot|(mac-address )?blackhole)[^A-Za-z0-9_\-] Green=[^A-Za-z0-9_\-](received|recovered|permit( vlan)?|(allow-pass|pvid|access|default|hybrid) vlan|allowed|enabled?|successful(ly)?|available|connected|up|yes|ok)[^A-Za-z0-9_\-] Yellow=[^A-Za-z0-9_\-]([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)[^A-Za-z0-9_\-] Blue=[^A-Za-z0-9]((interface )?(((X|Ten-|M-)?Gigabit|Fiber)?(Ethernet) ?|(100|40|25|X|F)?(G|T)?(E|i|ei)|(Twenty-Five|Forty|Hundred)?GigE ?|BAGG|NULL|LoopBack|Bridge-Aggregation|AggregatePort|Eth-Trunk|Vlan-interface|Vlanif)-?[0-9]+(\/[0-9]+)*|acl( (name [A-Za-z0-9&_-]+ [0-9]+|number ?[0-9]+))?|ospf( [0-9]+)?|(ip-pool|ip pool) [A-Za-z0-9 :"'\.\\\/&_\-]+)[^A-Za-z0-9] Magenta=[^A-Za-z0-9_\-]([0-9a-f][0-9a-f][0-9a-f][0-9a-f](:|-)[0-9a-f][0-9a-f][0-9a-f][0-9a-f](:|-)[0-9a-f][0-9a-f][0-9a-f][0-9a-f]|[0-9a-f][0-9a-f](:|-)[0-9a-f][0-9a-f](:|-)[0-9a-f][0-9a-f](:|-)[0-9a-f][0-9a-f](:|-)[0-9a-f][0-9a-f](:|-)[0-9a-f][0-9a-f]|(Please )?Press ENTER( to get started)?\.?)[^A-Za-z0-9_\-] Cyan=[^A-Za-z0-9_\-]((no|undo|description)( [A-Za-z0-9 :"'\.\\\/&_\-]+)?|acl [0-9]+|dhcp( (server( (enable|detect|database|option ?[0-9]+))?|select( interface)?|relay|enable|snooping))?|rule [0-9]+|source(-port)?|destination(-port)?|snmp-agent|import-route|traffic-policy [A-Za-z0-9_\-]+ (in|out)bound|protocol (in|out)bound [A-za-z]+|authentication-mode( (aaa|scheme|password))?|(un)?tagged)[^A-Za-z0-9_\-] Blinking= CaseSensitive=0 ================================================ FILE: DoNotProxy.list ================================================ DOMAIN-SUFFIX,local DOMAIN-SUFFIX,localhost DOMAIN-SUFFIX,internal DOMAIN-SUFFIX,lan DOMAIN-SUFFIX,ip6-localhost DOMAIN-SUFFIX,ip6-loopback IP-CIDR,0.0.0.0/8,no-resolve IP-CIDR,10.0.0.0/8,no-resolve IP-CIDR,100.64.0.0/10,no-resolve IP-CIDR,127.0.0.0/8,no-resolve IP-CIDR,169.254.0.0/16,no-resolve IP-CIDR,172.16.0.0/12,no-resolve IP-CIDR,192.168.0.0/16,no-resolve IP-CIDR,198.18.0.0/16,no-resolve IP-CIDR,224.0.0.0/4,no-resolve IP-CIDR6,::1/128,no-resolve IP-CIDR6,fc00::/7,no-resolve IP-CIDR6,fe80::/10,no-resolve IP-CIDR6,fd00::/8,no-resolve DOMAIN-KEYWORD,aria2 DOMAIN-KEYWORD,announce DOMAIN-KEYWORD,tracker DOMAIN,injections.adguard.org DOMAIN,local.adguard.org PROCESS-NAME,aria2c.exe PROCESS-NAME,fdm.exe PROCESS-NAME,Folx.exe PROCESS-NAME,NetTransport.exe PROCESS-NAME,Thunder.exe PROCESS-NAME,Transmission.exe PROCESS-NAME,uTorrent.exe PROCESS-NAME,Azureus.exe PROCESS-NAME,deluge.exe PROCESS-NAME,BitComet.exe PROCESS-NAME,BitComet_x64.exe PROCESS-NAME,qbittorrent.exe PROCESS-NAME,WebTorrent.exe PROCESS-NAME,WebTorrent Helper.exe PROCESS-NAME,rufus.exe PROCESS-NAME,DownloadService.exe PROCESS-NAME,Weiyun.exe PROCESS-NAME,BaiduNetdisk.exe PROCESS-NAME,aDrive.exe PROCESS-NAME,Quark.exe ================================================ FILE: DoProxy.list ================================================ # Basic on https://raw.githubusercontent.com/kn007/patch/master/bypass.list DOMAIN-SUFFIX,c.gle DOMAIN-SUFFIX,wallhaven.cc DOMAIN-SUFFIX,manhuagui.com DOMAIN-SUFFIX,catbox.moe DOMAIN-SUFFIX,mixtape.moe DOMAIN-SUFFIX,maxroll.gg DOMAIN-SUFFIX,simaware.ca DOMAIN-SUFFIX,arcgisonline.com DOMAIN-SUFFIX,rainviewer.com DOMAIN-SUFFIX,csb.app DOMAIN-SUFFIX,codesandbox.io DOMAIN-SUFFIX,subscriptionsmanagement-pa.googleapis.com DOMAIN-SUFFIX,subscriptionsmobile-pa.googleapis.com DOMAIN-SUFFIX,phosphor-pa.googleapis.com DOMAIN-SUFFIX,growth-pa.googleapis.com DOMAIN-SUFFIX,cloud.cupronickel.goog DOMAIN-SUFFIX,openai.com DOMAIN-SUFFIX,twitch.tv DOMAIN-SUFFIX,bose.com DOMAIN-SUFFIX,beautifyconverter.com DOMAIN-SUFFIX,worldofwarships.asia DOMAIN-SUFFIX,worldofwarships.jp DOMAIN-SUFFIX,wargaming.net DOMAIN-SUFFIX,hikarifield.co.jp DOMAIN-SUFFIX,postfix.org DOMAIN-SUFFIX,ietf.org DOMAIN-SUFFIX,schema.org DOMAIN-SUFFIX,youtu.be DOMAIN-SUFFIX,doubleclick.net DOMAIN-SUFFIX,googleadservices.com DOMAIN-SUFFIX,google.cn DOMAIN-SUFFIX,googleapps.com DOMAIN-SUFFIX,thinkwithgoogle.com DOMAIN-SUFFIX,withgoogle.com DOMAIN-SUFFIX,fbcdn.net DOMAIN-SUFFIX,flickr.com DOMAIN-SUFFIX,youtube-nocookie.com DOMAIN-SUFFIX,youtube.com DOMAIN-SUFFIX,googleusercontent.com DOMAIN-SUFFIX,gstatic.com DOMAIN-SUFFIX,thepiratebay.se DOMAIN-SUFFIX,gmail.com DOMAIN-SUFFIX,facebook.com DOMAIN-SUFFIX,vimeo.com DOMAIN-SUFFIX,twitter.com DOMAIN-SUFFIX,staticflickr.com DOMAIN-SUFFIX,sstatic.net DOMAIN-SUFFIX,imgur.com DOMAIN-SUFFIX,fastly.net DOMAIN-SUFFIX,bit.ly DOMAIN-SUFFIX,newrelic.com DOMAIN-SUFFIX,paypal.com DOMAIN-SUFFIX,paypalobjects.com DOMAIN-SUFFIX,cloudfront.net DOMAIN-SUFFIX,googlepages.com DOMAIN-SUFFIX,twimg.com DOMAIN-SUFFIX,t.co DOMAIN-SUFFIX,ytimg.com DOMAIN-SUFFIX,adzerk.net DOMAIN-SUFFIX,xmages.net DOMAIN-SUFFIX,fucklo.li DOMAIN-SUFFIX,freeproxylists.net DOMAIN-SUFFIX,archive.org DOMAIN-SUFFIX,shadowsocks.org DOMAIN-SUFFIX,dropbox.com DOMAIN-SUFFIX,blogspot.com DOMAIN-SUFFIX,rage4.com DOMAIN-SUFFIX,googleapis.com DOMAIN-SUFFIX,brandonchecketts.com DOMAIN-SUFFIX,googleratings.com DOMAIN-SUFFIX,letscorp.net DOMAIN-SUFFIX,googlevideo.com DOMAIN-SUFFIX,gravatar.com DOMAIN-SUFFIX,goods-pro.com DOMAIN-SUFFIX,w.org DOMAIN-SUFFIX,wp.com DOMAIN-SUFFIX,deviantart.com DOMAIN-SUFFIX,deviantart.net DOMAIN-SUFFIX,archive.fo DOMAIN-SUFFIX,blogger.com DOMAIN-SUFFIX,wordpress.com DOMAIN-SUFFIX,instagram.com DOMAIN-SUFFIX,facebook.net DOMAIN-SUFFIX,github.io DOMAIN-SUFFIX,github.com DOMAIN-SUFFIX,githubusercontent.com DOMAIN-SUFFIX,ssl-images-amazon.com DOMAIN-SUFFIX,associates-amazon.com DOMAIN-SUFFIX,media-amazon.com DOMAIN-SUFFIX,awsstatic.com DOMAIN-SUFFIX,googlecode.com DOMAIN-SUFFIX,live.com DOMAIN-SUFFIX,onedriver.com DOMAIN-SUFFIX,wikipedia.org DOMAIN-SUFFIX,*.wikimedia.org DOMAIN-SUFFIX,nodequery.com DOMAIN-SUFFIX,androidfilehost.com DOMAIN-SUFFIX,appspot.com DOMAIN-SUFFIX,pastebin.com DOMAIN-SUFFIX,dropboxstatic.com DOMAIN-SUFFIX,disq.us DOMAIN-SUFFIX,disquscdn.com DOMAIN-SUFFIX,disqus.com DOMAIN-SUFFIX,alexa.com DOMAIN-SUFFIX,amazonaws.com DOMAIN-SUFFIX,bbc.co.uk DOMAIN-SUFFIX,travis-ci.org DOMAIN-SUFFIX,pki.goog DOMAIN-SUFFIX,cdninstagram.com DOMAIN-SUFFIX,wordpress.org DOMAIN-SUFFIX,slideshare.net DOMAIN-SUFFIX,textnow.com DOMAIN-SUFFIX,gfx.ms DOMAIN-SUFFIX,chromium.org DOMAIN-SUFFIX,goo.gl DOMAIN-SUFFIX,googleblog.com DOMAIN-SUFFIX,wikileaks.org DOMAIN-SUFFIX,ggpht.com DOMAIN-SUFFIX,t.me DOMAIN-SUFFIX,telegram.me DOMAIN-SUFFIX,telegra.ph DOMAIN-SUFFIX,telegram.org DOMAIN-SUFFIX,tdesktop.com DOMAIN-SUFFIX,cisco.com DOMAIN-SUFFIX,oneplus.net DOMAIN-SUFFIX,g.co DOMAIN-SUFFIX,amazon.com DOMAIN-SUFFIX,xda-developers.com DOMAIN-SUFFIX,mysql.com DOMAIN-SUFFIX,tiny.cc DOMAIN-SUFFIX,speedyrails.net DOMAIN-SUFFIX,androidfilehost.com DOMAIN-SUFFIX,pinimg.com DOMAIN-SUFFIX,flightradar24.com DOMAIN-SUFFIX,dropboxusercontent.com DOMAIN-SUFFIX,cloudconvert.com DOMAIN-SUFFIX,steaminventoryhelper.com DOMAIN-SUFFIX,steamcardexchange.net DOMAIN-SUFFIX,steamcommunity.com DOMAIN-SUFFIX,steampowered.com DOMAIN-SUFFIX,steam-api.com DOMAIN-SUFFIX,akamaihd.net DOMAIN-SUFFIX,keycdn.com DOMAIN-SUFFIX,pumpcloud.net DOMAIN-SUFFIX,mega.nz DOMAIN-SUFFIX,mega.co.nz DOMAIN-SUFFIX,telesco.pe DOMAIN-SUFFIX,discordapp.net DOMAIN-SUFFIX,discordapp.com DOMAIN-SUFFIX,discord.com DOMAIN-SUFFIX,discord.gg DOMAIN-SUFFIX,redd.it DOMAIN-SUFFIX,reddit.com DOMAIN-SUFFIX,redditstatic.com DOMAIN-SUFFIX,redditmedia.com DOMAIN-SUFFIX,rawgit.com DOMAIN-SUFFIX,ampproject.net DOMAIN-SUFFIX,windy.com DOMAIN-SUFFIX,githubassets.com DOMAIN-SUFFIX,bootstrapcdn.com DOMAIN-SUFFIX,ubi.com DOMAIN-SUFFIX,ubisoft.com DOMAIN-SUFFIX,wonderpush.com DOMAIN-SUFFIX,goo.gl DOMAIN-SUFFIX,similarweb.com DOMAIN-SUFFIX,similarcdn.com DOMAIN-SUFFIX,intercom.io DOMAIN-SUFFIX,intercomcdn.com DOMAIN-SUFFIX,regex101.com DOMAIN-SUFFIX,jsfiddle.net DOMAIN-SUFFIX,jshell.net DOMAIN-SUFFIX,tunemymusic.com DOMAIN-SUFFIX,unsplash.com DOMAIN-SUFFIX,ping.pe DOMAIN-SUFFIX,spotify.com DOMAIN-SUFFIX,scdn.co DOMAIN-SUFFIX,3dmark.com DOMAIN-SUFFIX,futuremark.com DOMAIN-SUFFIX,ultravps.eu DOMAIN-SUFFIX,providerdienste.de DOMAIN-SUFFIX,virtualhosts.de DOMAIN-SUFFIX,he.net DOMAIN-SUFFIX,akamaized.net DOMAIN-SUFFIX,typekit.net DOMAIN-SUFFIX,akamaihd.net DOMAIN-SUFFIX,jquery.com DOMAIN-SUFFIX,ubi.li DOMAIN-SUFFIX,github.blog DOMAIN-SUFFIX,steamdb.info DOMAIN-SUFFIX,algolia.net DOMAIN-SUFFIX,steamstatic.com DOMAIN-SUFFIX,pixiv.net DOMAIN-SUFFIX,pximg.net DOMAIN-SUFFIX,pixnet.net DOMAIN-SUFFIX,pixfs.net DOMAIN-SUFFIX,sharemods.com DOMAIN-SUFFIX,notion.so DOMAIN-SUFFIX,imgbox.com DOMAIN-SUFFIX,geeks3d.com DOMAIN-SUFFIX,nvidia.com DOMAIN-SUFFIX,gamepadviewer.com DOMAIN-SUFFIX,humansofnewyork.com DOMAIN-SUFFIX,cloudflare.com DOMAIN-SUFFIX,wagnardsoft.com DOMAIN-SUFFIX,gitlab.com DOMAIN-SUFFIX,gitlab-static.net DOMAIN-SUFFIX,soundcloud.com DOMAIN-SUFFIX,sndcdn.com DOMAIN-SUFFIX,dl.sourceforge.net DOMAIN-SUFFIX,javmost.com DOMAIN-SUFFIX,javtrust.com DOMAIN-SUFFIX,jable.tv DOMAIN-SUFFIX,dmm.co.jp DOMAIN-SUFFIX,tomyangsh.pw DOMAIN-SUFFIX,rouman5.com DOMAIN-SUFFIX,rou.video DOMAIN-SUFFIX,htpt.cc DOMAIN-SUFFIX,soulvoice.club DOMAIN-SUFFIX,m-team.cc DOMAIN-SUFFIX,m-team.io DOMAIN-SUFFIX,hdcmct.org DOMAIN-SUFFIX,springsunday.net DOMAIN-SUFFIX,pterclub.com DOMAIN-SUFFIX,totheglory.im DOMAIN-SUFFIX,imgurl.org DOMAIN-SUFFIX,flycrow.pro DOMAIN-SUFFIX,groueta.cc DOMAIN-SUFFIX,mantou.biz DOMAIN-SUFFIX,ccache.org DOMAIN-SUFFIX,seejav.bid DOMAIN-SUFFIX,picturedata.org DOMAIN-SUFFIX,javbee.net DOMAIN-SUFFIX,bmp.ovh DOMAIN-SUFFIX,open.cd DOMAIN-SUFFIX,dmhy.org DOMAIN-SUFFIX,keepfrds.com DOMAIN-SUFFIX,jdbimgs.com DOMAIN-SUFFIX,gifyu.com DOMAIN-SUFFIX,dmmsee.fun DOMAIN-SUFFIX,bitvise.com DOMAIN-SUFFIX,chucklefish.org DOMAIN-SUFFIX,steamgames.com DOMAIN-SUFFIX,steamcontent.com DOMAIN-SUFFIX,steamusercontent.com DOMAIN-SUFFIX,fast.com DOMAIN-SUFFIX,netflix.com DOMAIN-SUFFIX,nflxvideo.net DOMAIN-SUFFIX,nflxext.com DOMAIN-SUFFIX,nflxso.net DOMAIN-SUFFIX,onetrust.com DOMAIN-SUFFIX,fosshub.com DOMAIN-SUFFIX,autodesk.com.cn DOMAIN-SUFFIX,autodesk.com DOMAIN-SUFFIX,autodesk.net DOMAIN-SUFFIX,openvpn.net DOMAIN-SUFFIX,webflow.io DOMAIN-SUFFIX,webflow.com DOMAIN-SUFFIX,pstorage.space DOMAIN-SUFFIX,netcdn.space DOMAIN-SUFFIX,boost.org DOMAIN-SUFFIX,tarolink.top DOMAIN-SUFFIX,o--o.xyz DOMAIN-SUFFIX,simgbb.com DOMAIN-SUFFIX,imgbb.com DOMAIN-SUFFIX,ibb.co DOMAIN-SUFFIX,z4a.net DOMAIN-SUFFIX,iili.io DOMAIN-SUFFIX,weserv.nl DOMAIN-SUFFIX,shoot.photo DOMAIN-SUFFIX,ccp.ovh DOMAIN-SUFFIX,imagebam.com DOMAIN-SUFFIX,truenas.com DOMAIN-SUFFIX,themoneyconverter.com DOMAIN-SUFFIX,jpopsuki.eu DOMAIN-SUFFIX,skyvector.com DOMAIN-SUFFIX,postimgs.org DOMAIN-SUFFIX,postlmg.cc DOMAIN-SUFFIX,imagecurl.com DOMAIN-SUFFIX,simbrief.com DOMAIN-SUFFIX,flightsim.to DOMAIN-SUFFIX,flybywiresim.com DOMAIN-SUFFIX,chartfox.org DOMAIN-SUFFIX,openstreetmap.org DOMAIN-SUFFIX,vatsim.net DOMAIN-SUFFIX,navigraph.com DOMAIN-SUFFIX,gog-statics.com DOMAIN-SUFFIX,gog.com DOMAIN-SUFFIX,zendesk.com DOMAIN-SUFFIX,zdassets.com DOMAIN-SUFFIX,avsim.com DOMAIN-SUFFIX,sda1.dev DOMAIN-SUFFIX,sl.al DOMAIN-SUFFIX,ccimg.xyz DOMAIN-SUFFIX,admod.com DOMAIN-SUFFIX,web.dev DOMAIN-SUFFIX,jsdelivr.net DOMAIN-SUFFIX,jsdelivr.com DOMAIN-SUFFIX,evga.com DOMAIN-SUFFIX,adultempire.com DOMAIN-SUFFIX,imageshack.com DOMAIN-SUFFIX,exoticaz.to DOMAIN-SUFFIX,storages.cc DOMAIN-SUFFIX,rockstargames.com DOMAIN-SUFFIX,arkoselabs.com DOMAIN-SUFFIX,arkoselabs.cn DOMAIN-SUFFIX,javhdporn.net DOMAIN-SUFFIX,i18n.pw DOMAIN-SUFFIX,qbittorrent.org DOMAIN-SUFFIX,seejav.bid DOMAIN-SUFFIX,jdbstatic.com DOMAIN-SUFFIX,021jf.com DOMAIN-SUFFIX,pic599.net DOMAIN-SUFFIX,98tuch.net DOMAIN-SUFFIX,postto.me DOMAIN-SUFFIX,aspnetcdn.com DOMAIN-SUFFIX,netlify.app DOMAIN-SUFFIX,netlify.com DOMAIN-SUFFIX,healthchecks.io DOMAIN-SUFFIX,mgstage.com DOMAIN-SUFFIX,gvt2.com DOMAIN-SUFFIX,gstatic.com DOMAIN-SUFFIX,googleapis.com DOMAIN-SUFFIX,gmodules.com DOMAIN-SUFFIX,googlesyndication.com DOMAIN-SUFFIX,sesexiaozhan.com DOMAIN-SUFFIX,biquge.tw DOMAIN-SUFFIX,acgnx.se DOMAIN-SUFFIX,combot.org DOMAIN-SUFFIX,tenor.com DOMAIN-SUFFIX,pixeldrain.com DOMAIN-SUFFIX,gamer.com.tw DOMAIN-SUFFIX,bahamut.com.tw DOMAIN-SUFFIX,seiya-saiga.com DOMAIN-SUFFIX,ptpimg.me DOMAIN-SUFFIX,wiki.gg DOMAIN-SUFFIX,1024search.tk DOMAIN-SUFFIX,1080.tw DOMAIN-SUFFIX,1688.com.au DOMAIN-SUFFIX,1dpw.com DOMAIN-SUFFIX,2008xianzhang.info DOMAIN-SUFFIX,24smile.org DOMAIN-SUFFIX,4shared.com DOMAIN-SUFFIX,5i01.com DOMAIN-SUFFIX,5z5.com DOMAIN-SUFFIX,64memo.com DOMAIN-SUFFIX,64tianwang.com DOMAIN-SUFFIX,64wiki.com DOMAIN-SUFFIX,666kb.com DOMAIN-SUFFIX,6do.news DOMAIN-SUFFIX,6park.com DOMAIN-SUFFIX,a1080hd.com DOMAIN-SUFFIX,abc.xyz DOMAIN-SUFFIX,ablwang.com DOMAIN-SUFFIX,aboluowang.com DOMAIN-SUFFIX,actimes.com.au DOMAIN-SUFFIX,adsafeprotected.com DOMAIN-SUFFIX,adsrvr.org DOMAIN-SUFFIX,adultblogranking.com DOMAIN-SUFFIX,aforcemorepowerful.org DOMAIN-SUFFIX,ahd1080.com DOMAIN-SUFFIX,aisex.com DOMAIN-SUFFIX,aishangyou.tube DOMAIN-SUFFIX,ait.org.tw DOMAIN-SUFFIX,alabout.com DOMAIN-SUFFIX,alicejapan.co.jp DOMAIN-SUFFIX,aliengu.com DOMAIN-SUFFIX,alliance.org.hk DOMAIN-SUFFIX,allinfa.com DOMAIN-SUFFIX,am730.com.hk DOMAIN-SUFFIX,amazon.co.jp DOMAIN-SUFFIX,amazonwebapps.com DOMAIN-SUFFIX,amnesty.org DOMAIN-SUFFIX,amnesty.tw DOMAIN-SUFFIX,ananass.fr DOMAIN-SUFFIX,android.com DOMAIN-SUFFIX,androidfilehost.com DOMAIN-SUFFIX,animecrazy.net DOMAIN-SUFFIX,anti1984.com DOMAIN-SUFFIX,anygoing.com DOMAIN-SUFFIX,aomiwang.com DOMAIN-SUFFIX,aoqinet.com DOMAIN-SUFFIX,apkmirror.com DOMAIN-SUFFIX,app2.hkatv.com DOMAIN-SUFFIX,appledaily.hk DOMAIN-SUFFIX,appspot.com DOMAIN-SUFFIX,archive.org DOMAIN-SUFFIX,asahichinese.com DOMAIN-SUFFIX,asianews.it DOMAIN-SUFFIX,atchinese.com DOMAIN-SUFFIX,atdmt.com DOMAIN-SUFFIX,atgfw.org DOMAIN-SUFFIX,aurl.mobi DOMAIN-SUFFIX,ausdaily.net.au DOMAIN-SUFFIX,autoit-cdn.com DOMAIN-SUFFIX,autoitscript.com DOMAIN-SUFFIX,avbbs.tv DOMAIN-SUFFIX,avcity.tv DOMAIN-SUFFIX,avlang.com DOMAIN-SUFFIX,av-scouter.info DOMAIN-SUFFIX,avsp2p.com DOMAIN-SUFFIX,backchina.com DOMAIN-SUFFIX,backtotiananmen.com DOMAIN-SUFFIX,badongo.com DOMAIN-SUFFIX,baisex.me DOMAIN-SUFFIX,bannedbook.org DOMAIN-SUFFIX,bayfiles.net DOMAIN-SUFFIX,bbcchinese.com DOMAIN-SUFFIX,bbg.gov DOMAIN-SUFFIX,bcchinese.net DOMAIN-SUFFIX,beijingspring.com DOMAIN-SUFFIX,bet365.com DOMAIN-SUFFIX,betanews.com DOMAIN-SUFFIX,beyondfirewall.com DOMAIN-SUFFIX,bind9.net DOMAIN-SUFFIX,binux.me DOMAIN-SUFFIX,bit.ly DOMAIN-SUFFIX,bitshare.com DOMAIN-SUFFIX,bitsnoop.com DOMAIN-SUFFIX,biz.tm DOMAIN-SUFFIX,bjs.org DOMAIN-SUFFIX,bjzc.org DOMAIN-SUFFIX,blinkx.com DOMAIN-SUFFIX,blockedinchina.net DOMAIN-SUFFIX,blog.jp DOMAIN-SUFFIX,blog.xuite.net DOMAIN-SUFFIX,blog.yam.com DOMAIN-SUFFIX,blogblog.com DOMAIN-SUFFIX,blogcatalog.com DOMAIN-SUFFIX,blogcity.me DOMAIN-SUFFIX,blogimg.jp DOMAIN-SUFFIX,bloglines.com DOMAIN-SUFFIX,bloglovin.com DOMAIN-SUFFIX,blogs.com DOMAIN-SUFFIX,blogspot.in DOMAIN-SUFFIX,blogspot.kr DOMAIN-SUFFIX,bmvflorida.us DOMAIN-SUFFIX,bod.asia DOMAIN-SUFFIX,book.com.tw DOMAIN-SUFFIX,books.com.tw DOMAIN-SUFFIX,botanwang.com DOMAIN-SUFFIX,botanwang.org DOMAIN-SUFFIX,bowenpress.com DOMAIN-SUFFIX,boxun.com DOMAIN-SUFFIX,boxun.us DOMAIN-SUFFIX,break.com DOMAIN-SUFFIX,brizzly.com DOMAIN-SUFFIX,btdigg.org DOMAIN-SUFFIX,btku.org DOMAIN-SUFFIX,btn.weather.ca DOMAIN-SUFFIX,bud.org.tw DOMAIN-SUFFIX,buff.ly DOMAIN-SUFFIX,bullog.org DOMAIN-SUFFIX,bullogger.com DOMAIN-SUFFIX,businessweek.com DOMAIN-SUFFIX,bwp.im DOMAIN-SUFFIX,bx.tl DOMAIN-SUFFIX,c000.me DOMAIN-SUFFIX,c080.me DOMAIN-SUFFIX,c800.me DOMAIN-SUFFIX,c9x.info DOMAIN-SUFFIX,cahr.org.tw DOMAIN-SUFFIX,campaign.tw-npo.org DOMAIN-SUFFIX,cams.org.sg DOMAIN-SUFFIX,canyu.org DOMAIN-SUFFIX,caochangqing.com DOMAIN-SUFFIX,cap.org.hk DOMAIN-SUFFIX,cari.com.my DOMAIN-SUFFIX,caribbeancom.com DOMAIN-SUFFIX,catholic.org.hk DOMAIN-SUFFIX,catholic.org.tw DOMAIN-SUFFIX,catwizard.net DOMAIN-SUFFIX,cbc.ca DOMAIN-SUFFIX,ccdtr.org DOMAIN-SUFFIX,ccim.org DOMAIN-SUFFIX,ccw.org.tw DOMAIN-SUFFIX,cdef.org DOMAIN-SUFFIX,cdjp.org DOMAIN-SUFFIX,cdnews.com.tw DOMAIN-SUFFIX,cdns.com.tw DOMAIN-SUFFIX,cecc.gov DOMAIN-SUFFIX,cenews.eu DOMAIN-SUFFIX,centralnation.com DOMAIN-SUFFIX,cfhks.org.hk DOMAIN-SUFFIX,cgdepot.org DOMAIN-SUFFIX,chicagoncmtv.com DOMAIN-SUFFIX,china.ucanews.com DOMAIN-SUFFIX,chinaaid.net DOMAIN-SUFFIX,chinabiz.org.tw DOMAIN-SUFFIX,chinadigitaltimes.net DOMAIN-SUFFIX,chinaelections.com DOMAIN-SUFFIX,chinaelections.org DOMAIN-SUFFIX,chinaeweekly.com DOMAIN-SUFFIX,chinafile.com DOMAIN-SUFFIX,chinagfw.org DOMAIN-SUFFIX,chinainperspective.com DOMAIN-SUFFIX,chinapress.com.my DOMAIN-SUFFIX,chinarightsia.org DOMAIN-SUFFIX,chinatcc.gov.cn DOMAIN-SUFFIX,china-week.com DOMAIN-SUFFIX,chinaworker.info DOMAIN-SUFFIX,chinesedaily.com DOMAIN-SUFFIX,chinesenewsnet.com DOMAIN-SUFFIX,chinesepen.org DOMAIN-SUFFIX,chosun.com DOMAIN-SUFFIX,christianstudy.com DOMAIN-SUFFIX,christiantimes.org.hk DOMAIN-SUFFIX,chrlawyers.hk DOMAIN-SUFFIX,chrome.com DOMAIN-SUFFIX,chromium.org DOMAIN-SUFFIX,chubun.com DOMAIN-SUFFIX,cincainews.com DOMAIN-SUFFIX,citizenlab.org DOMAIN-SUFFIX,citizensradio.org DOMAIN-SUFFIX,city9x.com DOMAIN-SUFFIX,civicparty.hk DOMAIN-SUFFIX,civilhrfront.org DOMAIN-SUFFIX,civilmedia.tw DOMAIN-SUFFIX,clickme.net DOMAIN-SUFFIX,cna.com.tw DOMAIN-SUFFIX,cnd.org DOMAIN-SUFFIX,cnliberals.com DOMAIN-SUFFIX,cnn.com DOMAIN-SUFFIX,cnyes.com DOMAIN-SUFFIX,codeproject.com DOMAIN-SUFFIX,comefromchina.com DOMAIN-SUFFIX,competitionforce.hk DOMAIN-SUFFIX,cool18.com DOMAIN-SUFFIX,coolloud.org.tw DOMAIN-SUFFIX,cotweet.com DOMAIN-SUFFIX,crazys.cc DOMAIN-SUFFIX,creaders.net DOMAIN-SUFFIX,creadersnet.com DOMAIN-SUFFIX,crwdcntrl.net DOMAIN-SUFFIX,c--spanarchives.-org DOMAIN-SUFFIX,c-spanvideo.org DOMAIN-SUFFIX,cts.com.tw DOMAIN-SUFFIX,cw.com.tw DOMAIN-SUFFIX,d100.net DOMAIN-SUFFIX,d2pass.com DOMAIN-SUFFIX,dadazim.com DOMAIN-SUFFIX,dailymotion.com DOMAIN-SUFFIX,dalailamaworld.com DOMAIN-SUFFIX,danwei.org DOMAIN-SUFFIX,daolan.net DOMAIN-SUFFIX,del.icio.us DOMAIN-SUFFIX,democraticchina.org DOMAIN-SUFFIX,democrats.org DOMAIN-SUFFIX,de-sci.org DOMAIN-SUFFIX,dfs.kuaipan.cn DOMAIN-SUFFIX,dfzdaili.com DOMAIN-SUFFIX,dieneueepoche.com DOMAIN-SUFFIX,digg.com DOMAIN-SUFFIX,digitalocean.com DOMAIN-SUFFIX,digitalvolcano.co.uk DOMAIN-SUFFIX,diigo.com DOMAIN-SUFFIX,dipity.com DOMAIN-SUFFIX,discuss.com.hk DOMAIN-SUFFIX,disp.cc DOMAIN-SUFFIX,disqus.com DOMAIN-SUFFIX,djjsq.com DOMAIN-SUFFIX,djorz.com DOMAIN-SUFFIX,dnscrypt.org DOMAIN-SUFFIX,doit.im DOMAIN-SUFFIX,dolc.de DOMAIN-SUFFIX,dolf.org.hk DOMAIN-SUFFIX,dongtaiwang.com DOMAIN-SUFFIX,doub.io DOMAIN-SUFFIX,doubibackup.com DOMAIN-SUFFIX,download.aircrack-ng.org DOMAIN-SUFFIX,dphk.org DOMAIN-SUFFIX,dpp.org.tw DOMAIN-SUFFIX,dropbox.com DOMAIN-SUFFIX,dropboxusercontent.com DOMAIN-SUFFIX,drsunacademy.com DOMAIN-SUFFIX,dtiblog.com DOMAIN-SUFFIX,duga.jp DOMAIN-SUFFIX,duihua.org DOMAIN-SUFFIX,duping.net DOMAIN-SUFFIX,dupola.com DOMAIN-SUFFIX,dw.com DOMAIN-SUFFIX,dw.de DOMAIN-SUFFIX,dw-world.com DOMAIN-SUFFIX,dw-world.de DOMAIN-SUFFIX,dxlive.com DOMAIN-SUFFIX,e123.hk DOMAIN-SUFFIX,ebookbrowse.com DOMAIN-SUFFIX,ecfa.org.tw DOMAIN-SUFFIX,echinanews.com.tw DOMAIN-SUFFIX,edge.liveleak.com DOMAIN-SUFFIX,edicypages.com DOMAIN-SUFFIX,edoors.com DOMAIN-SUFFIX,efcc.org.hk DOMAIN-SUFFIX,eic-av.com DOMAIN-SUFFIX,e-info.org.tw DOMAIN-SUFFIX,elpais.com DOMAIN-SUFFIX,emilylau.org.hk DOMAIN-SUFFIX,erabaru.net DOMAIN-SUFFIX,erights.net DOMAIN-SUFFIX,eroantenna.com DOMAIN-SUFFIX,ero-video.net DOMAIN-SUFFIX,es-visiontimes.com DOMAIN-SUFFIX,etaiwannews.com DOMAIN-SUFFIX,ettoday.net DOMAIN-SUFFIX,extremetube.com DOMAIN-SUFFIX,extremetube.phncdn.com DOMAIN-SUFFIX,facebook.com DOMAIN-SUFFIX,facebook.net DOMAIN-SUFFIX,fangeming.com DOMAIN-SUFFIX,fanqiang.network DOMAIN-SUFFIX,fanqianghou.com DOMAIN-SUFFIX,farxian.com DOMAIN-SUFFIX,fastly.net DOMAIN-SUFFIX,faststone.org DOMAIN-SUFFIX,favstar.fm DOMAIN-SUFFIX,faydao.com DOMAIN-SUFFIX,fb.me DOMAIN-SUFFIX,fbcdn.net DOMAIN-SUFFIX,fc2.com DOMAIN-SUFFIX,fdc89.jp DOMAIN-SUFFIX,feedburner.com DOMAIN-SUFFIX,feedjit.com DOMAIN-SUFFIX,feedsportal.com DOMAIN-SUFFIX,felixcat.net DOMAIN-SUFFIX,ffx.io DOMAIN-SUFFIX,filecroco.com DOMAIN-SUFFIX,filesor.com DOMAIN-SUFFIX,filestube.com DOMAIN-SUFFIX,firebaseio.com DOMAIN-SUFFIX,firepic.org DOMAIN-SUFFIX,flyzy2005.com DOMAIN-SUFFIX,fmnnow.com DOMAIN-SUFFIX,foofind.is DOMAIN-SUFFIX,fooooo.com DOMAIN-SUFFIX,forum.kaiyuan.de DOMAIN-SUFFIX,forum.tvb.com DOMAIN-SUFFIX,fqrouter.com DOMAIN-SUFFIX,free4u.com.ar DOMAIN-SUFFIX,freebrowser.org DOMAIN-SUFFIX,freedomhouse.org DOMAIN-SUFFIX,freeproxyserver.net DOMAIN-SUFFIX,freeshadow.info DOMAIN-SUFFIX,freetufu.com DOMAIN-SUFFIX,freewechat.com DOMAIN-SUFFIX,freeweibo.com DOMAIN-SUFFIX,friendfeed.com DOMAIN-SUFFIX,fring.com DOMAIN-SUFFIX,frontlinedefenders.org DOMAIN-SUFFIX,fukugan.com DOMAIN-SUFFIX,fuli.ba DOMAIN-SUFFIX,fullyillustrated.com DOMAIN-SUFFIX,fungchiwood.com DOMAIN-SUFFIX,fw.cm DOMAIN-SUFFIX,fw.com DOMAIN-SUFFIX,g.co DOMAIN-SUFFIX,gaeproxy.com DOMAIN-SUFFIX,gamebase.com.tw DOMAIN-SUFFIX,gameclub.tw DOMAIN-SUFFIX,ganges.com DOMAIN-SUFFIX,gcpnews.com DOMAIN-SUFFIX,geocities.co.jp DOMAIN-SUFFIX,getfoxyproxy.org DOMAIN-SUFFIX,getgom.com DOMAIN-SUFFIX,getsync.com DOMAIN-SUFFIX,ggpht.com DOMAIN-SUFFIX,ghd1080.com DOMAIN-SUFFIX,gigacircle.com DOMAIN-SUFFIX,github.com DOMAIN-SUFFIX,git-scm.com DOMAIN-SUFFIX,gittigidiyor.com DOMAIN-SUFFIX,globalvoices.org DOMAIN-SUFFIX,globalvoicesonline.org DOMAIN-SUFFIX,glorystar.me DOMAIN-SUFFIX,gmail.com DOMAIN-SUFFIX,gnews.org DOMAIN-SUFFIX,goagent.biz DOMAIN-SUFFIX,goo.gl DOMAIN-SUFFIX,google.com DOMAIN-SUFFIX,googlecode.com DOMAIN-SUFFIX,googlesource.com DOMAIN-SUFFIX,googletagmanager.com DOMAIN-SUFFIX,googleusercontent.com DOMAIN-SUFFIX,gopetition.com DOMAIN-SUFFIX,gospelherald.com DOMAIN-SUFFIX,gospelherald.com.hk DOMAIN-SUFFIX,gov.tw DOMAIN-SUFFIX,gpass1.com DOMAIN-SUFFIX,greatfire.org DOMAIN-SUFFIX,greatfirewallofchina.org DOMAIN-SUFFIX,greatzhonghua.org DOMAIN-SUFFIX,greenparty.org.tw DOMAIN-SUFFIX,gvm.com.tw DOMAIN-SUFFIX,h528.com DOMAIN-SUFFIX,haixiainfo.com.tw DOMAIN-SUFFIX,hakkatv.org.tw DOMAIN-SUFFIX,hav.tv DOMAIN-SUFFIX,have8.com DOMAIN-SUFFIX,h-china.org DOMAIN-SUFFIX,hcocoa.com DOMAIN-SUFFIX,hd1080.org DOMAIN-SUFFIX,hdtransform.com DOMAIN-SUFFIX,hechaji.com DOMAIN-SUFFIX,helpzhuling.org DOMAIN-SUFFIX,heqinglian.net DOMAIN-SUFFIX,hetnieuwetijdperk.com DOMAIN-SUFFIX,hexieshe.com DOMAIN-SUFFIX,hikinggfw.org DOMAIN-SUFFIX,hilive.tv DOMAIN-SUFFIX,hioz.org DOMAIN-SUFFIX,hitwister.com DOMAIN-SUFFIX,hjav.org DOMAIN-SUFFIX,hjclub.info DOMAIN-SUFFIX,hk01.com DOMAIN-SUFFIX,hkatvnews.com DOMAIN-SUFFIX,hkchurch.org DOMAIN-SUFFIX,hkci.org.hk DOMAIN-SUFFIX,hkcnews.com DOMAIN-SUFFIX,hkcrm.org.hk DOMAIN-SUFFIX,hkdailynews.com.hk DOMAIN-SUFFIX,hkdash.com DOMAIN-SUFFIX,hkej.com DOMAIN-SUFFIX,hket.com DOMAIN-SUFFIX,hketgroup.com DOMAIN-SUFFIX,hkgolden.com DOMAIN-SUFFIX,hkgoldenmobile.com DOMAIN-SUFFIX,hkhkhk.com DOMAIN-SUFFIX,hkhrm.org.hk DOMAIN-SUFFIX,hkja.org.hk DOMAIN-SUFFIX,hkjc.com DOMAIN-SUFFIX,hkjp.org DOMAIN-SUFFIX,hkptu.org DOMAIN-SUFFIX,hk-pub.com DOMAIN-SUFFIX,hkreporter.com DOMAIN-SUFFIX,hksilicon.com DOMAIN-SUFFIX,hkumall.com DOMAIN-SUFFIX,hkupop.hku.hk DOMAIN-SUFFIX,hkusu.org DOMAIN-SUFFIX,hkwcc.org.hk DOMAIN-SUFFIX,hongkongfp.com DOMAIN-SUFFIX,hongkongtibetfilmfestival2015.com DOMAIN-SUFFIX,hotchyx.com DOMAIN-SUFFIX,hotspotshield.com DOMAIN-SUFFIX,hrichina.org DOMAIN-SUFFIX,hrw.org DOMAIN-SUFFIX,huanghuagang.org DOMAIN-SUFFIX,huaren.us DOMAIN-SUFFIX,huaxia-news.com DOMAIN-SUFFIX,huping.net DOMAIN-SUFFIX,hutong9.net DOMAIN-SUFFIX,hwinfo.com DOMAIN-SUFFIX,hxmmdd.com DOMAIN-SUFFIX,hyperrate.com DOMAIN-SUFFIX,hzy.pw DOMAIN-SUFFIX,i1.hk DOMAIN-SUFFIX,i2ocr.com DOMAIN-SUFFIX,i2p2.de DOMAIN-SUFFIX,iask.ca DOMAIN-SUFFIX,iceimg.com DOMAIN-SUFFIX,icij.org DOMAIN-SUFFIX,idol-mile.com DOMAIN-SUFFIX,idv.tw DOMAIN-SUFFIX,ifanqiang.com DOMAIN-SUFFIX,ift.tt DOMAIN-SUFFIX,igfw.??? DOMAIN-SUFFIX,igfw.tk DOMAIN-SUFFIX,igossip.com DOMAIN-SUFFIX,ihao.org DOMAIN-SUFFIX,ihd1080.org DOMAIN-SUFFIX,ihktv.com DOMAIN-SUFFIX,imagebam.com DOMAIN-SUFFIX,imageshack.us DOMAIN-SUFFIX,imagestorming.com DOMAIN-SUFFIX,imageurlhost.com DOMAIN-SUFFIX,imagevenue.com DOMAIN-SUFFIX,imagezilla.net DOMAIN-SUFFIX,img.ly DOMAIN-SUFFIX,imgchili.com DOMAIN-SUFFIX,imgchili.net DOMAIN-SUFFIX,imgdino.com DOMAIN-SUFFIX,imgkeep.com DOMAIN-SUFFIX,imgly.net DOMAIN-SUFFIX,imgtiger.com DOMAIN-SUFFIX,immoral.jp DOMAIN-SUFFIX,inmediahk.net DOMAIN-SUFFIX,inote.tw DOMAIN-SUFFIX,inside.com.tw DOMAIN-SUFFIX,instagram.com DOMAIN-SUFFIX,insynchq.com DOMAIN-SUFFIX,intermargins.net DOMAIN-SUFFIX,internet.org DOMAIN-SUFFIX,internetfreedom.org DOMAIN-SUFFIX,inxian.com DOMAIN-SUFFIX,ipcf.org.tw DOMAIN-SUFFIX,ipicture.ru DOMAIN-SUFFIX,ipkmedia.com DOMAIN-SUFFIX,isohunt.com DOMAIN-SUFFIX,isunaffairs.com DOMAIN-SUFFIX,isuntv.com DOMAIN-SUFFIX,ithelp.ithome.com.tw DOMAIN-SUFFIX,ixxx.com DOMAIN-SUFFIX,iyouport.com DOMAIN-SUFFIX,iyouport.org DOMAIN-SUFFIX,j.mp DOMAIN-SUFFIX,jasonsavard.com DOMAIN-SUFFIX,jav008.com DOMAIN-SUFFIX,javblog.biz DOMAIN-SUFFIX,javfree.me DOMAIN-SUFFIX,javideo.info DOMAIN-SUFFIX,javsharing.com DOMAIN-SUFFIX,jbtalks.cc DOMAIN-SUFFIX,jinbushe.org DOMAIN-SUFFIX,jingfeng.info DOMAIN-SUFFIX,jingpin.org DOMAIN-SUFFIX,jiruan.net DOMAIN-SUFFIX,jjgirls.com DOMAIN-SUFFIX,jkforum.net DOMAIN-SUFFIX,jpavgod.com DOMAIN-SUFFIX,justin.tv DOMAIN-SUFFIX,just-ping.com DOMAIN-SUFFIX,kan.center DOMAIN-SUFFIX,kankan.today DOMAIN-SUFFIX,keakon.net DOMAIN-SUFFIX,keephkshining.com DOMAIN-SUFFIX,kenengba.com DOMAIN-SUFFIX,kexueshangwang.info DOMAIN-SUFFIX,kinghost.com DOMAIN-SUFFIX,kingstone.com.tw DOMAIN-SUFFIX,kir.jp DOMAIN-SUFFIX,kwongwah.com.my DOMAIN-SUFFIX,la-forum.org DOMAIN-SUFFIX,lagranepoca.com DOMAIN-SUFFIX,lailaibt.com DOMAIN-SUFFIX,laqingdan.net DOMAIN-SUFFIX,latteye.com DOMAIN-SUFFIX,lcx.cc DOMAIN-SUFFIX,lefora.com DOMAIN-SUFFIX,left21.hk DOMAIN-SUFFIX,lemonde.fr DOMAIN-SUFFIX,lesoir.be DOMAIN-SUFFIX,letscorp.net DOMAIN-SUFFIX,libertytimes.com.tw DOMAIN-SUFFIX,limbopro.xyz DOMAIN-SUFFIX,lineageosrom.com DOMAIN-SUFFIX,line-scdn.net DOMAIN-SUFFIX,linkbucks.com DOMAIN-SUFFIX,listhub.net DOMAIN-SUFFIX,liuxiaobo.net DOMAIN-SUFFIX,liveleak.com DOMAIN-SUFFIX,livestation.com DOMAIN-SUFFIX,livestream.com DOMAIN-SUFFIX,localpresshk.com DOMAIN-SUFFIX,lockerz.com DOMAIN-SUFFIX,lolbin.net DOMAIN-SUFFIX,loli.net DOMAIN-SUFFIX,longhair.hk DOMAIN-SUFFIX,lookpic.com DOMAIN-SUFFIX,loved.hk DOMAIN-SUFFIX,lrip.org DOMAIN-SUFFIX,lsd.org.hk DOMAIN-SUFFIX,lsj1080.cc DOMAIN-SUFFIX,lsj1080.net DOMAIN-SUFFIX,lsj1080.tv DOMAIN-SUFFIX,ltn.com.tw DOMAIN-SUFFIX,lvv2.com DOMAIN-SUFFIX,mail-archive.com DOMAIN-SUFFIX,maiplus.com DOMAIN-SUFFIX,malaymail.com DOMAIN-SUFFIX,malaysiakini.com DOMAIN-SUFFIX,matters.news DOMAIN-SUFFIX,mattwilcox.net DOMAIN-SUFFIX,medium.com DOMAIN-SUFFIX,mefeedia.com DOMAIN-SUFFIX,memehk.com DOMAIN-SUFFIX,merit-times.com DOMAIN-SUFFIX,merit-times.com.tw DOMAIN-SUFFIX,merlinblog.xyz DOMAIN-SUFFIX,metrolife.ca DOMAIN-SUFFIX,metroradio.com.hk DOMAIN-SUFFIX,minghui.org DOMAIN-SUFFIX,mingjinglishi.com DOMAIN-SUFFIX,mingjingnews.com DOMAIN-SUFFIX,mingpaonews.com DOMAIN-SUFFIX,mingshengbao.com DOMAIN-SUFFIX,minus.com DOMAIN-SUFFIX,minzhuzhongguo.org DOMAIN-SUFFIX,mirrorbooks.com DOMAIN-SUFFIX,mitbbs.com DOMAIN-SUFFIX,mkini.net DOMAIN-SUFFIX,mlxiaoshuo.com DOMAIN-SUFFIX,mobile01.com DOMAIN-SUFFIX,mobileways.de DOMAIN-SUFFIX,mobypicture.com DOMAIN-SUFFIX,moedict.tw DOMAIN-SUFFIX,mojim.com DOMAIN-SUFFIX,mokeedev.com DOMAIN-SUFFIX,molihua.org DOMAIN-SUFFIX,mono.ac DOMAIN-SUFFIX,mono.sh DOMAIN-SUFFIX,moonbbs.com DOMAIN-SUFFIX,mozilla.net DOMAIN-SUFFIX,mp3ye.eu DOMAIN-SUFFIX,mpfinance.com DOMAIN-SUFFIX,msguancha.com DOMAIN-SUFFIX,mtlmp4.com DOMAIN-SUFFIX,myca168.com DOMAIN-SUFFIX,mychinanews.com DOMAIN-SUFFIX,mycnnews.com DOMAIN-SUFFIX,mycould.com DOMAIN-SUFFIX,myfreecams.com DOMAIN-SUFFIX,myfreshnet.com DOMAIN-SUFFIX,myhd1080.tv DOMAIN-SUFFIX,myradio.com.hk DOMAIN-SUFFIX,myradio.hk DOMAIN-SUFFIX,mysinablog.com DOMAIN-SUFFIX,nanyang.com DOMAIN-SUFFIX,nanyangpost.com DOMAIN-SUFFIX,ncchinesenews.com DOMAIN-SUFFIX,ndr.de DOMAIN-SUFFIX,net1.hkbu.edu.hk DOMAIN-SUFFIX,netgear.com DOMAIN-SUFFIX,netme.cc DOMAIN-SUFFIX,network54.com DOMAIN-SUFFIX,networkedblogs.com DOMAIN-SUFFIX,newcenturymc.com DOMAIN-SUFFIX,newcenturynews.com DOMAIN-SUFFIX,newhighlandvision.com DOMAIN-SUFFIX,news.hk.msn.com DOMAIN-SUFFIX,news.pts.org.tw DOMAIN-SUFFIX,newsancai.com DOMAIN-SUFFIX,newstapa.org DOMAIN-SUFFIX,newtaiwan.com.tw DOMAIN-SUFFIX,newtalk.tw DOMAIN-SUFFIX,nextdigital.com.hk DOMAIN-SUFFIX,nextmag.com.tw DOMAIN-SUFFIX,nextmedia.com DOMAIN-SUFFIX,ngensis.com DOMAIN-SUFFIX,nicovideo.jp DOMAIN-SUFFIX,nobel.se DOMAIN-SUFFIX,nobelprize.org DOMAIN-SUFFIX,notipage.com DOMAIN-SUFFIX,nownews.com DOMAIN-SUFFIX,nps.gov DOMAIN-SUFFIX,nrk.no DOMAIN-SUFFIX,ntd.tv DOMAIN-SUFFIX,ntdtv.com DOMAIN-SUFFIX,ntdtv-dc.com DOMAIN-SUFFIX,nuzcom.com DOMAIN-SUFFIX,nvquan.org DOMAIN-SUFFIX,nyt.com DOMAIN-SUFFIX,nytcn.me DOMAIN-SUFFIX,nyti.ms DOMAIN-SUFFIX,nytimes.com DOMAIN-SUFFIX,nytimg.com DOMAIN-SUFFIX,nytstyle.com DOMAIN-SUFFIX,observechina.net DOMAIN-SUFFIX,oclp.hk DOMAIN-SUFFIX,ogaoga.org DOMAIN-SUFFIX,oiktv.com DOMAIN-SUFFIX,olx.com.br DOMAIN-SUFFIX,on.cc DOMAIN-SUFFIX,onedrive.live.com DOMAIN-SUFFIX,onlinestuffs.com DOMAIN-SUFFIX,open.com.hk DOMAIN-SUFFIX,opendemocracy.net DOMAIN-SUFFIX,orientaldaily.com.my DOMAIN-SUFFIX,orientaldaily.on.cc DOMAIN-SUFFIX,orzhd.com DOMAIN-SUFFIX,oursogo.com DOMAIN-SUFFIX,oursteps.com.au DOMAIN-SUFFIX,outbrain.com DOMAIN-SUFFIX,ow.ly DOMAIN-SUFFIX,oyax.com DOMAIN-SUFFIX,oyou.com.au DOMAIN-SUFFIX,page2rss.com DOMAIN-SUFFIX,pageflakes.com DOMAIN-SUFFIX,panoramio.com DOMAIN-SUFFIX,pao-pao.net DOMAIN-SUFFIX,paper.li DOMAIN-SUFFIX,passiontimes.hk DOMAIN-SUFFIX,pbwiki.com DOMAIN-SUFFIX,pbworks.com DOMAIN-SUFFIX,pcdvd.com.tw DOMAIN-SUFFIX,pchome.com.tw DOMAIN-SUFFIX,pcij.org DOMAIN-SUFFIX,peacehall.com DOMAIN-SUFFIX,penchinese.org DOMAIN-SUFFIX,peopo.org DOMAIN-SUFFIX,perfspot.com DOMAIN-SUFFIX,pfd.org.hk DOMAIN-SUFFIX,picpar.com DOMAIN-SUFFIX,picrar.com DOMAIN-SUFFIX,piebridge.me DOMAIN-SUFFIX,pimgs.com DOMAIN-SUFFIX,pincong.rocks DOMAIN-SUFFIX,ping.fm DOMAIN-SUFFIX,pinimg.com DOMAIN-SUFFIX,pinterest.com DOMAIN-SUFFIX,piratescreen.com DOMAIN-SUFFIX,piring.com DOMAIN-SUFFIX,pixshock.net DOMAIN-SUFFIX,playno1.com DOMAIN-SUFFIX,playno1.com.tw DOMAIN-SUFFIX,popvote.hk DOMAIN-SUFFIX,popyard.com DOMAIN-SUFFIX,popyard.org DOMAIN-SUFFIX,porn.com DOMAIN-SUFFIX,pornhub.com DOMAIN-SUFFIX,post852.com DOMAIN-SUFFIX,posterous.com DOMAIN-SUFFIX,potatso.com DOMAIN-SUFFIX,potatsocontent.com DOMAIN-SUFFIX,powerlinks.com DOMAIN-SUFFIX,premeforwindows.com DOMAIN-SUFFIX,prestige-av.com DOMAIN-SUFFIX,prettyvirgin.com DOMAIN-SUFFIX,privoxy.org DOMAIN-SUFFIX,pubu.com.tw DOMAIN-SUFFIX,qidian.ca DOMAIN-SUFFIX,qiwen.lu DOMAIN-SUFFIX,quickpornsearch.com DOMAIN-SUFFIX,quora.com DOMAIN-SUFFIX,quoracdn.net DOMAIN-SUFFIX,qxbbs.org DOMAIN-SUFFIX,radioaustralia.net.au DOMAIN-SUFFIX,ranyunfei.com DOMAIN-SUFFIX,rapbull.net DOMAIN-SUFFIX,rcinet.ca DOMAIN-SUFFIX,readingtimes.com.tw DOMAIN-SUFFIX,recovery.org.tw DOMAIN-SUFFIX,redchinacn.org DOMAIN-SUFFIX,reddit.com DOMAIN-SUFFIX,redsquirrel87.com DOMAIN-SUFFIX,redtube.com DOMAIN-SUFFIX,reduik.com DOMAIN-SUFFIX,referer.us DOMAIN-SUFFIX,relink.us DOMAIN-SUFFIX,rendsmap.com DOMAIN-SUFFIX,renminbao.com DOMAIN-SUFFIX,resilio.com DOMAIN-SUFFIX,restorehk.com DOMAIN-SUFFIX,reuters.com DOMAIN-SUFFIX,reutersmedia.net DOMAIN-SUFFIX,rfa.org DOMAIN-SUFFIX,rferl.org DOMAIN-SUFFIX,rfi.fr DOMAIN-SUFFIX,rfi.my DOMAIN-SUFFIX,rghost.net DOMAIN-SUFFIX,riku.me DOMAIN-SUFFIX,rmjdw.com DOMAIN-SUFFIX,rnw.nl DOMAIN-SUFFIX,rocmp.org DOMAIN-SUFFIX,roodo.com DOMAIN-SUFFIX,rsf.org DOMAIN-SUFFIX,rsf-chinese.org DOMAIN-SUFFIX,rthk.hk DOMAIN-SUFFIX,rthk.org.hk DOMAIN-SUFFIX,rti.org.tw DOMAIN-SUFFIX,s3.amazonaws.com DOMAIN-SUFFIX,sadpanda.us DOMAIN-SUFFIX,sanmin.com.tw DOMAIN-SUFFIX,sanminjiaoliu.net DOMAIN-SUFFIX,savemedia.com DOMAIN-SUFFIX,savetube.com DOMAIN-SUFFIX,savevid.com DOMAIN-SUFFIX,saveyoutube.com DOMAIN-SUFFIX,scdn.co DOMAIN-SUFFIX,scholarism.com DOMAIN-SUFFIX,s-dragon.org DOMAIN-SUFFIX,search.xxx DOMAIN-SUFFIX,secretchina.com DOMAIN-SUFFIX,securityinabox.org DOMAIN-SUFFIX,securitykiss.com DOMAIN-SUFFIX,sendspace.com DOMAIN-SUFFIX,setn.com DOMAIN-SUFFIX,sex.com DOMAIN-SUFFIX,sexinsex.net DOMAIN-SUFFIX,shadow.ma DOMAIN-SUFFIX,shadowgov.tw DOMAIN-SUFFIX,shadowsocks.org DOMAIN-SUFFIX,sharenxs.com DOMAIN-SUFFIX,sharpdaily.com DOMAIN-SUFFIX,sharpdaily.tw DOMAIN-SUFFIX,sherrychan.net DOMAIN-SUFFIX,shizhao.org DOMAIN-SUFFIX,shr.lc DOMAIN-SUFFIX,shutterstock.com DOMAIN-SUFFIX,sinaapp.co DOMAIN-SUFFIX,singtao.com DOMAIN-SUFFIX,sinomontreal.ca DOMAIN-SUFFIX,sinoquebec.com DOMAIN-SUFFIX,sis001.com DOMAIN-SUFFIX,sitebro.tw DOMAIN-SUFFIX,six-degrees.io DOMAIN-SUFFIX,slideshare.net DOMAIN-SUFFIX,smh.com.au DOMAIN-SUFFIX,smhric.org DOMAIN-SUFFIX,softether-download.com DOMAIN-SUFFIX,sohcradio.com DOMAIN-SUFFIX,sondelespoir.org DOMAIN-SUFFIX,sonidodelaesperanza.org DOMAIN-SUFFIX,sougouwiki.com DOMAIN-SUFFIX,soundofhope.co.kr DOMAIN-SUFFIX,soundofhope.kr DOMAIN-SUFFIX,soundofhope.org DOMAIN-SUFFIX,soup-dev.com DOMAIN-SUFFIX,southnews.com.tw DOMAIN-SUFFIX,spankwire.com DOMAIN-SUFFIX,spotify.com DOMAIN-SUFFIX,spring4u.info DOMAIN-SUFFIX,ssr.tools DOMAIN-SUFFIX,startpage.com DOMAIN-SUFFIX,steemit.com DOMAIN-SUFFIX,stgloballink.com DOMAIN-SUFFIX,stickeraction.com DOMAIN-SUFFIX,stimme-de.de DOMAIN-SUFFIX,stooorage.com DOMAIN-SUFFIX,storify.com DOMAIN-SUFFIX,storm.mg DOMAIN-SUFFIX,stormmediagroup.com DOMAIN-SUFFIX,strongvpn.com DOMAIN-SUFFIX,stumbleupon.com DOMAIN-SUFFIX,stupidvideos.com DOMAIN-SUFFIX,sucuri.net DOMAIN-SUFFIX,sugarsync.com DOMAIN-SUFFIX,sun1911.com DOMAIN-SUFFIX,supersu.com DOMAIN-SUFFIX,swagbucks.com DOMAIN-SUFFIX,sydneytoday.com DOMAIN-SUFFIX,t.co DOMAIN-SUFFIX,t.me DOMAIN-SUFFIX,t66y.com DOMAIN-SUFFIX,taaze.tw DOMAIN-SUFFIX,tahr.org.tw DOMAIN-SUFFIX,taiwandaily.net DOMAIN-SUFFIX,taiwanus.net DOMAIN-SUFFIX,talkonly.net DOMAIN-SUFFIX,tantannews.com DOMAIN-SUFFIX,tavis.tw DOMAIN-SUFFIX,tca.org.tw DOMAIN-SUFFIX,tdesktop.com DOMAIN-SUFFIX,techinasia.com DOMAIN-SUFFIX,technorati.com DOMAIN-SUFFIX,telegra.ph DOMAIN-SUFFIX,telegram.me DOMAIN-SUFFIX,telegram.org DOMAIN-SUFFIX,telesco.pe DOMAIN-SUFFIX,tenacy.com DOMAIN-SUFFIX,tenacy-free.com DOMAIN-SUFFIX,theblaze.com DOMAIN-SUFFIX,thebobs.com DOMAIN-SUFFIX,theepochtimes.com DOMAIN-SUFFIX,thefrontier.hk DOMAIN-SUFFIX,theglobalmail.org DOMAIN-SUFFIX,theguardian.com DOMAIN-SUFFIX,thehousenews.com DOMAIN-SUFFIX,theinitium.com DOMAIN-SUFFIX,thenewslens.com DOMAIN-SUFFIX,the-sun.on.cc DOMAIN-SUFFIX,thetibetpost.com DOMAIN-SUFFIX,thetimenow.com DOMAIN-SUFFIX,thinkingtaiwan.com DOMAIN-SUFFIX,thisav.com DOMAIN-SUFFIX,tiananmenduizhi.com DOMAIN-SUFFIX,tiananmenmother.org DOMAIN-SUFFIX,tiananmenuniv.com DOMAIN-SUFFIX,tiananmenuniv.net DOMAIN-SUFFIX,tibet.net DOMAIN-SUFFIX,time.com DOMAIN-SUFFIX,timer-tab.com DOMAIN-SUFFIX,tiny.cc DOMAIN-SUFFIX,tinychat.com DOMAIN-SUFFIX,tmagazine.com DOMAIN-SUFFIX,tmu.edu.tw DOMAIN-SUFFIX,tokyocn.com DOMAIN-SUFFIX,topcoo.com DOMAIN-SUFFIX,topsy.com DOMAIN-SUFFIX,torrentcrazy.com DOMAIN-SUFFIX,torrentkitty.com DOMAIN-SUFFIX,trackon.org DOMAIN-SUFFIX,transformativeworks.org DOMAIN-SUFFIX,transparency.org DOMAIN-SUFFIX,trendsmap.com DOMAIN-SUFFIX,trib.al DOMAIN-SUFFIX,trouw.nl DOMAIN-SUFFIX,trtc.com.tw DOMAIN-SUFFIX,trueimages.ru DOMAIN-SUFFIX,truveo.com DOMAIN-SUFFIX,tsquare.tv DOMAIN-SUFFIX,tsu.org.tw DOMAIN-SUFFIX,tube8.com DOMAIN-SUFFIX,tuiyun.net DOMAIN-SUFFIX,tumblr.com DOMAIN-SUFFIX,tv.com DOMAIN-SUFFIX,tvboxnow.com DOMAIN-SUFFIX,twbbs.org DOMAIN-SUFFIX,twbbs.tw DOMAIN-SUFFIX,tweetdeck.com DOMAIN-SUFFIX,tweettunnel.com DOMAIN-SUFFIX,tweez.net DOMAIN-SUFFIX,twgreatdaily.com DOMAIN-SUFFIX,twicsy.com DOMAIN-SUFFIX,twilk.com DOMAIN-SUFFIX,twimg.com DOMAIN-SUFFIX,twipple.jp DOMAIN-SUFFIX,twitchtv.com DOMAIN-SUFFIX,twitiq.com DOMAIN-SUFFIX,twitpic.com DOMAIN-SUFFIX,twitter.com DOMAIN-SUFFIX,twitterfeed.com DOMAIN-SUFFIX,twittergadget.com DOMAIN-SUFFIX,twitterknowhow.com DOMAIN-SUFFIX,twitvid.com DOMAIN-SUFFIX,twitvid.com.edgesuite.net DOMAIN-SUFFIX,twiyia.com DOMAIN-SUFFIX,twtrland.com DOMAIN-SUFFIX,ucdc1998.org DOMAIN-SUFFIX,udn.com DOMAIN-SUFFIX,udn.com.tw DOMAIN-SUFFIX,ugo.com DOMAIN-SUFFIX,uhrp.org DOMAIN-SUFFIX,uighurbiz.net DOMAIN-SUFFIX,ulifestyle.com.hk DOMAIN-SUFFIX,ultrareach.com DOMAIN-SUFFIX,unblock.cn.com DOMAIN-SUFFIX,uncyclopedia.tw DOMAIN-SUFFIX,upmedia.mg DOMAIN-SUFFIX,uproxy.org DOMAIN-SUFFIX,ustream.tv DOMAIN-SUFFIX,utorrent.com DOMAIN-SUFFIX,uwants.com DOMAIN-SUFFIX,uyghuramerican.org DOMAIN-SUFFIX,uyghurpress.com DOMAIN-SUFFIX,uyl.me DOMAIN-SUFFIX,v2ex.com DOMAIN-SUFFIX,van698.com DOMAIN-SUFFIX,vaticanradio.org DOMAIN-SUFFIX,vdonext.com DOMAIN-SUFFIX,velkaepocha.sk DOMAIN-SUFFIX,venchina.com DOMAIN-SUFFIX,veoh.com DOMAIN-SUFFIX,vevo.com DOMAIN-SUFFIX,viber.com DOMAIN-SUFFIX,video.pbs.org DOMAIN-SUFFIX,vietdaikynguyen.com DOMAIN-SUFFIX,vinmusic.com DOMAIN-SUFFIX,visiontimes.ca DOMAIN-SUFFIX,vjmedia.com.hk DOMAIN-SUFFIX,vocus.cc DOMAIN-SUFFIX,vot.org DOMAIN-SUFFIX,vox-cdn.com DOMAIN-SUFFIX,vpngate.jp DOMAIN-SUFFIX,vpngate.net DOMAIN-SUFFIX,vpnxunlu.com DOMAIN-SUFFIX,wahas.com DOMAIN-SUFFIX,wanglixiong.com DOMAIN-SUFFIX,want-daily.com DOMAIN-SUFFIX,watchzerg.com DOMAIN-SUFFIX,watorrent.org DOMAIN-SUFFIX,wattpad.com DOMAIN-SUFFIX,wdf5.com DOMAIN-SUFFIX,wearn.com DOMAIN-SUFFIX,web.pts.org.tw DOMAIN-SUFFIX,weblagu.com DOMAIN-SUFFIX,websitepulse.com DOMAIN-SUFFIX,webwarper.net DOMAIN-SUFFIX,wegfw.com DOMAIN-SUFFIX,weibosuite.com DOMAIN-SUFFIX,weijingsheng.org DOMAIN-SUFFIX,weiquanwang.org DOMAIN-SUFFIX,wengewang.org DOMAIN-SUFFIX,wenxuecity.com DOMAIN-SUFFIX,westca.com DOMAIN-SUFFIX,westkit.net DOMAIN-SUFFIX,wetransfer.com DOMAIN-SUFFIX,whattalking.com DOMAIN-SUFFIX,whogovernstw.org DOMAIN-SUFFIX,whotalking.com DOMAIN-SUFFIX,wi-gadget.com DOMAIN-SUFFIX,wikia.com DOMAIN-SUFFIX,wikia.net DOMAIN-SUFFIX,wikia.nocookie.net DOMAIN-SUFFIX,wikia-beacon.com DOMAIN-SUFFIX,wiztechnologies.jp DOMAIN-SUFFIX,wn.com DOMAIN-SUFFIX,worldcat.org DOMAIN-SUFFIX,worldjournal.com DOMAIN-SUFFIX,wrchina.org DOMAIN-SUFFIX,wretch.cc DOMAIN-SUFFIX,wuala.com DOMAIN-SUFFIX,wuerkaixi.com DOMAIN-SUFFIX,wujie.net DOMAIN-SUFFIX,wujieliulan.com DOMAIN-SUFFIX,wwitv.com DOMAIN-SUFFIX,xanga.com DOMAIN-SUFFIX,xbookcn.net DOMAIN-SUFFIX,xhamster.com DOMAIN-SUFFIX,xiaochuncnjp.com DOMAIN-SUFFIX,xinmiao.com.hk DOMAIN-SUFFIX,xinyubbs.net DOMAIN-SUFFIX,xiti.com DOMAIN-SUFFIX,xizang-zhiye.org DOMAIN-SUFFIX,xjp.cc DOMAIN-SUFFIX,xn--fiq681d48s.org DOMAIN-SUFFIX,xpics.us DOMAIN-SUFFIX,xtube.com DOMAIN-SUFFIX,xuehua.us DOMAIN-SUFFIX,xunluvpn.com DOMAIN-SUFFIX,xvideosmatome.net DOMAIN-SUFFIX,xys.org DOMAIN-SUFFIX,yamedia.tw DOMAIN-SUFFIX,yasni.co.uk DOMAIN-SUFFIX,ycp.hk DOMAIN-SUFFIX,yfrog.com DOMAIN-SUFFIX,yhritw.org DOMAIN-SUFFIX,yibaochina.com DOMAIN-SUFFIX,yidio.com DOMAIN-SUFFIX,yorkbbs.ca DOMAIN-SUFFIX,you2go.me DOMAIN-SUFFIX,youjizz.com DOMAIN-SUFFIX,youmaker.com DOMAIN-SUFFIX,youporn.com DOMAIN-SUFFIX,youthwant.com.tw DOMAIN-SUFFIX,youtu.be DOMAIN-SUFFIX,youtube.com DOMAIN-SUFFIX,youtubeinmp3.com DOMAIN-SUFFIX,ytimg.com DOMAIN-SUFFIX,yzzk.com DOMAIN-SUFFIX,zhengjian.org DOMAIN-SUFFIX,zhenlibu1984.com DOMAIN-SUFFIX,zhongzilou.com DOMAIN-SUFFIX,zhoushuguang.com DOMAIN-SUFFIX,zinio.com DOMAIN-SUFFIX,zlvc.net DOMAIN-SUFFIX,zlvc.net.he2.aqb.so DOMAIN-SUFFIX,zomobo.net DOMAIN-SUFFIX,zorpia.com DOMAIN-SUFFIX,zuo.la DOMAIN-SUFFIX,zuobiao.me DOMAIN-SUFFIX,zuola.com DOMAIN-SUFFIX,zxing.org DOMAIN-SUFFIX,zyzc.greatzhonghua.org ================================================ FILE: Enable_BoringSSL_OCSP.patch ================================================ From: CarterLi Date: Sat, 19 May 2018 22:08:47 +0800 Subject: [PATCH] Support OSCP stapling on BoringSSL Link: https://github.com/kn007/patch/issues/4 Modified: kn007 diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c index 0bea5e7..334f1c2 100644 --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -1874,8 +1874,50 @@ ngx_int_t ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify) { +#ifdef BORINGSSL_MAKE_DELETER + ngx_log_error(NGX_LOG_NOTICE, ssl->log, 0, + "using boringssl, currently only \"ssl_stapling_file\" is supported. use it as your own risk"); + + BIO *bio; + int len; + u_char buf[2048]; + + if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) { + return NGX_ERROR; + } + + bio = BIO_new_file((char *) file->data, "r"); + if (bio == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "BIO_new_file(\"%s\") failed", file->data); + return NGX_ERROR; + } + + len = BIO_read(bio, buf, sizeof(buf) / sizeof(u_char)); + BIO_free(bio); + bio = NULL; + + if (len <= 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "Read OCSP response file \"%s\" failed: %d", file->data, len); + return NGX_ERROR; + } + + if (len >= 2000) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "Unexpected OCSP response file length: %d", len); + return NGX_ERROR; + } + + if (!SSL_CTX_set_ocsp_response(ssl->ctx, buf, len)) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set_ocsp_response(ssl->ctx, buf, %d) failed", len); + return NGX_ERROR; + } +#else ngx_log_error(NGX_LOG_WARN, ssl->log, 0, "\"ssl_stapling\" ignored, not supported"); +#endif return NGX_OK; } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Karl Chen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Patches ## Nginx ### use_openssl_md5_sha1.patch * Use the OpenSSL library instead of the Nginx original function. * Repack it because "patch unexpectedly ends in middle of line". - Thanks [@CarterLi](https://github.com/kn007/patch/issues/5) Test pass: 1.29.2 ### nginx_dynamic_tls_records.patch * Add Dynamic TLS Record Support. Require: Nginx 1.29.2 Test pass: 1.29.2 ### Enable_BoringSSL_OCSP.patch * For BoringSSL support OCSP stapling. - Using "ssl_stapling_file" to support. - Only "ssl_stapling_file" with single cert is supported. - Auto-rebuild OCSP stapling file with shell and atd(at cron), you can read this [article](https://kn007.net/topics/let-nginx-support-ocsp-stapling-when-using-boringssl/)(Maybe you need a translation tool). - Thanks [@CarterLi](https://github.com/kn007/patch/issues/4). Test pass: 1.25.5 ### nginx.patch (Discontinued) * Add HTTP2 HPACK Encoding Support. * Add Dynamic TLS Record Support. Require: Nginx 1.25.0 (this version only) Test pass: 1.25.0 Since `Nginx` 1.25.1, HPACK encoding will not support because the HTTP/2 server push support has been removed. ### nginx_with_quic.patch (Discontinued) * Add HTTP3(QUIC) Support. - For OCSP stapling, maybe you need [this](https://github.com/kn007/patch#enable_boringssl_ocsppatch). * Add HTTP2 HPACK Encoding Support. * Add Dynamic TLS Record Support. Require: Nginx 1.21.4 or later. Test pass: 1.23.3 with [cloudflare/quiche@c9311a1](https://github.com/cloudflare/quiche/tree/c9311a18910c0277867c34c0acc4a9711b50b913) Check your modules when build failed. ### nginx_for_1.23.4.patch (Deprecated) * Add HTTP2 HPACK Encoding Support. * Add Dynamic TLS Record Support. Require: Nginx version below 1.25.0 Test pass: 1.23.4 ### nginx_with_quic_for_1.19.7_full.patch (Deprecated) * Add HTTP3(QUIC) Support. - For OCSP stapling, maybe you need [this](https://github.com/kn007/patch#enable_boringssl_ocsppatch). * Add HTTP2 HPACK Encoding Support. * Add Dynamic TLS Record Support. Require: Nginx 1.19.7 or later(below 1.21.4). Test pass: 1.21.3 with [cloudflare/quiche@af1bbc0](https://github.com/cloudflare/quiche/tree/af1bbc03e9992bae516d0b692a481de64bd4e8d9) `nginx_with_quic_for_1.19.6.patch` is required to support Nginx versions lower than 1.19.7, cause `post_accept_timeout` had been removed by Nginx since 1.19.7. ### nginx_with_quic_for_1.19.6.patch (Deprecated) * Revert `nginx_with_quic.patch` to support Nginx versions lower than 1.19.7. * Patch `nginx_with_quic.patch` first, then patch this one. Test pass: 1.19.6 with [nginx_with_quic.patch@ec8cac4](https://github.com/kn007/patch/blob/ec8cac4fc74b1718e9b005e7533201aec552aa40/nginx_with_quic.patch) & [cloudflare/quiche@fca5e9a](https://github.com/cloudflare/quiche/tree/fca5e9acdfdff9e80c7b9346214c64b393108328) ### nginx_with_spdy.patch (Deprecated) * Add SPDY Support. * Add HTTP2 HPACK Encoding Support. * Add Dynamic TLS Record Support. Test pass: 1.17.9 ### nginx_with_spdy_quic.patch (Deprecated) * Add SPDY Support. * Add HTTP3(QUIC) Support. * Add HTTP2 HPACK Encoding Support. * Add Dynamic TLS Record Support. Test pass: 1.17.9 with [cloudflare/quiche@9a8b3b](https://github.com/cloudflare/quiche/tree/9a8b3b12d007715cd4cc254362db51d5a01de9f2) ## Other ### openssl-1.1.1.patch * Add TLS 1.3 Support. * Add BoringSSL's Equal Preference Support. * Add ChaCha20-Poly1305 Draft Version Support. Test pass: 1.1.1w ### ffmpeg-let-rtmp-flv-support-hevc-h265-opus.patch * FLV/RTMP Extensions For FFmpeg. - Add FLV Encode/Decode with H.265/HEVC & OPUS Codec Support. - Add RTMP Stream Push with H.265/HEVC & OPUS Codec Support. - Thanks [@xia-chu](https://github.com/xia-chu/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81). Test pass: 4.3.1 ### dropbox_fs_fix.patch * For Dropbox Linux users. This patch could let official python script auto-load `libdropbox_fs_fix.so` library before start dropboxd. - Using [Dropbox filesystem fix for Linux Repo](https://github.com/dark/dropbox-filesystem-fix) and make `libdropbox_fs_fix.so`. - After compiled, copy `libdropbox_fs_fix.so` to `$HOME/.dropbox-dist/libdropbox_fs_fix.so`. - Download Dropbox official python script, put it with patch file together. - Patch, enjoy. Test pass: 2019.02.14 version ## Links [聊聊Nginx 1.25和HTTP/3](https://kn007.net/topics/talk-about-nginx-1-25-and-http-3/) [Nginx 1.19.4新特性推荐](https://kn007.net/topics/nginx-1-19-4-new-feature-recommendation/) [让Nginx使用BoringSSL时支持OCSP Stapling](https://kn007.net/topics/let-nginx-support-ocsp-stapling-when-using-boringssl/) [博客终止使用TLS 1.0和TLS 1.1协议](https://kn007.net/topics/deprecating-tls-1-0-and-tls-1-1-protocols/) [小试HTTP3](https://kn007.net/topics/try-http3/) [我的Nginx编译之旅](https://kn007.net/topics/my-nginx-compilation-tour/) [解决Dropbox Linux客户端因文件系统导致无法同步问题](https://kn007.net/topics/fix-dropbox-filesystem-sync-problem-for-linux-client/) [kn007的个人博客](https://kn007.net) ================================================ FILE: blacklist.dgjy ================================================ dmad.info pin.hamturer.com sim.miniast.com tongjii.us slushpool.com mlmy.3322.org fget-career.com 3666777a.com gp.like383.com tj.gogo2021.xyz webmine.cz arthur.niria.biz apple-pie.in althawry.org www.careerdesk.org www.hiprofitnetworks.com nvhaaa.top ttzytp.com xiusebf1.com hpdwfd2.com rfyqtv2.com qbyyvg3.com kqvkvc3.com bfrmye5.com fpvdxd5.com mjrvkv5.com nrxduw5.com rzgvdm5.com upffxs6.com exwytd7.com dfwskw7.com pvhgws7.com gezkdx7.com qczuqw8.com kupfkc9.com rrtwda9.com unpfqc9.com ahmediye.net park.realbig.online a.51qingmiao.com.kaxi.net wxy398.com xred.mooo.com gp.miaoxia123.com dz.qd388.cn tyszlga.wweczyc.com lrepacks.net img.yparse.com amsamex.com jsecoin.com up.2nike.org g2.arrowhitech.com ampyazilim.com.tr scriptcc.cc beibeixixi.f3322.org ok2.lovehy.com tp1jcgl644jk.com s6_down.listw.top s6_req.listw.top z100.vip jxn0.51ginkgo.com www.zigui.org init.icloud-analysis.com ftp.byethost7.com ftp.byethost10.com files.000webhost.com 000webhostapp.com a7788.1apps.com attach10132.1apps.com bluemountain.1apps.com filer1.1apps.com s8877.1apps.com ugig.ir ceye.io share.dmca.gripe 9bys.com amsamex.com jwhss.com prothid.ny.us.gamesurge.net gameservers.nj.us.gamesurge.net api.ceye.io ipfs.via0.com themoviehometheather.com gcdz.info brontocdn.com via0.com 765567.xyz 7521.com go2cliks.net electladyproductions.com haifti.com wtkbxx.com tesyk.com tesjj.com tesiu.com saxyit.com xx-x.catdggga.com catdggga.com exitgames.com avdog-l0597.vip avdog.net cdnupdateservice.com 9988948.com mmo2350.top gb.dyabgjaf.com shunyi520.vicp.net ji.kunkunonline.top mycontrolpanel29.com dasan.one iluearv.net ysuw.xyz udxuke.net dh.alyun.cn uiwixv.net iuayeu.net dark-confusion.com zerostdio.com iachks.net contabostorage.com cacen.despacito5.com ================================================ FILE: bypass.list ================================================ [SwitchyOmega Conditions] ; Require: SwitchyOmega >= 2.3.2 ; Date: 2026/3/28 ; Usage: https://github.com/FelisCatus/SwitchyOmega/wiki/RuleListUsage ; https://raw.githubusercontent.com/kn007/patch/master/bypass.list *.c.gle *.wallhaven.cc *.manhuagui.com *.catbox.moe *.mixtape.moe *.maxroll.gg *.simaware.ca *.arcgisonline.com *.rainviewer.com subscriptionsmanagement-pa.googleapis.com subscriptionsmobile-pa.googleapis.com phosphor-pa.googleapis.com growth-pa.googleapis.com *.csb.app *.codesandbox.io *.cloud.cupronickel.goog *.openai.com *.twitch.tv *.bose.com *.worldofwarships.asia *.worldofwarships.jp *.wargaming.net *.hikarifield.co.jp *.postfix.org *.ietf.org *.schema.org *.youtu.be *.doubleclick.net *.googleadservices.com *.google.cn *.googleapps.com *.thinkwithgoogle.com *.withgoogle.com *.fbcdn.net *.flickr.com *.youtube-nocookie.com *.youtube.com *.googleusercontent.com *.thepiratebay.se *.gmail.com *.google.com* *.facebook.com *.vimeo.com *.twitter.com *.staticflickr.com *.sstatic.net *.imgur.com *.fastly.net *.bit.ly *.newrelic.com *.paypal.com *.paypalobjects.com *.cloudfront.net *.googlepages.com *.twimg.com *.t.co *.ytimg.com *.adzerk.net *.xmages.net *.fucklo.li *.freeproxylists.net *.archive.org *.shadowsocks.org *.dropbox.com *.blogspot.com *.rage4.com *.brandonchecketts.com *.googleratings.com *.letscorp.net *.googlevideo.com *.gravatar.com *.goods-pro.com *.w.org *.wp.com *.deviantart.com *.deviantart.net *.archive.fo *.blogger.com *.wordpress.com *.instagram.com *.facebook.net *.github.io *.github.com *.githubusercontent.com *.ssl-images-amazon.com *.associates-amazon.com *.media-amazon.com *.awsstatic.com *.googlecode.com *.live.com *.onedriver.com *.wikipedia.org *.wikimedia.org *.nodequery.com *.androidfilehost.com *.appspot.com *.pastebin.com *.dropboxstatic.com *.disq.us *.disquscdn.com *.disqus.com *.alexa.com *.amazonaws.com *.bbc.co.uk *.travis-ci.org *.*.google *.goog *.pki.goog *.cdninstagram.com *.wordpress.org *.slideshare.net *.textnow.com *.gfx.ms *.chromium.org *.goo.gl *.googleblog.com *.wikileaks.org *.google.co.* *.ggpht.com *.t.me *.telegram.me *.telegra.ph *.telegram.org *.tdesktop.com *.cisco.com *.oneplus.net *.g.co *.amazon.com *.amazon.co.* *.xda-developers.com *.mysql.com *.tiny.cc *.speedyrails.net *.androidfilehost.com *.pinimg.com *.flightradar24.com *.dropboxusercontent.com *.cloudconvert.com *.steaminventoryhelper.com *.steamcardexchange.net *.steamcommunity.com *.steampowered.com *.steam-api.com *.akamaihd.net *.keycdn.com *.pumpcloud.net *.mega.nz *.mega.co.nz *.telesco.pe *.discordapp.net *.discordapp.com *.discord.com *.discord.gg *.redd.it *.reddit.com *.redditstatic.com *.redditmedia.com *.rawgit.com *.ampproject.net *.windy.com *.githubassets.com *.bootstrapcdn.com *.ubi.com *.ubisoft.com *.wonderpush.com *.goo.gl *.similarweb.com *.similarcdn.com *.intercom.io *.intercomcdn.com *.regex101.com *.jsfiddle.net *.jshell.net *.tunemymusic.com *.unsplash.com *.ping.pe *.spotify.com *.scdn.co *.3dmark.com *.futuremark.com *.ultravps.eu *.providerdienste.de *.virtualhosts.de *.he.net *.akamaized.net *.typekit.net *.akamaihd.net *.jquery.com *.ubi.li *.github.blog *.steamdb.info *.algolia.net *.steamstatic.com *.pixiv.net *.pximg.net *.pixnet.net *.pixfs.net *.sharemods.com *.notion.so *.imgbox.com *.geeks3d.com *.nvidia.com *.gamepadviewer.com *.humansofnewyork.com *.cloudflare.com *.wagnardsoft.com *.gitlab.com *.gitlab-static.net *.soundcloud.com *.sndcdn.com *.dl.sourceforge.net *.javmost.com *.javtrust.com *.jable.tv *.dmm.co.jp *.tomyangsh.pw *.rouman5.com *.rou.video *.htpt.cc *.soulvoice.club *.m-team.cc *.m-team.io *.hdcmct.org *.springsunday.net *.pterclub.com *.totheglory.im *.imgurl.org *.flycrow.pro *.groueta.cc *.mantou.biz *.ccache.org *.seejav.bid *.picturedata.org *.javbee.net *.bmp.ovh *.open.cd *.dmhy.org *.keepfrds.com *.jdbimgs.com *.gifyu.com *.dmmsee.fun *.bitvise.com *.chucklefish.org *.steamgames.com *.steamcontent.com *.steamusercontent.com *.fast.com *.netflix.com *.nflxvideo.net *.nflxext.com *.nflxso.net *.onetrust.com *.fosshub.com *.autodesk.com.cn *.autodesk.com *.autodesk.net *.openvpn.net *.webflow.io *.webflow.com *.pstorage.space *.netcdn.space *.boost.org *.tarolink.top *.o--o.xyz *.simgbb.com *.imgbb.com *.ibb.co *.z4a.net *.iili.io *.weserv.nl *.shoot.photo *.ccp.ovh *.imagebam.com *.truenas.com *.themoneyconverter.com *.jpopsuki.eu *.skyvector.com *.postimgs.org *.postlmg.cc *.imagecurl.com *.simbrief.com *.flightsim.to *.flybywiresim.com *.chartfox.org *.openstreetmap.org *.vatsim.net *.navigraph.com *.gog-statics.com *.gog.com *.zendesk.com *.zdassets.com *.avsim.com *.sda1.dev *.sl.al *.ccimg.xyz *.admod.com *.web.dev *.jsdelivr.net *.jsdelivr.com *.evga.com *.imageshack.com *.adultempire.com *.exoticaz.to *.storages.cc *.rockstargames.com *.arkoselabs.com *.arkoselabs.cn *.javhdporn.net *.i18n.pw *.greasyfork.org *.qbittorrent.org *.seejav.bid *.jdbstatic.com *.021jf.com *.pic599.net *.98tuch.net *.postto.me *.aspnetcdn.com *.netlify.app *.netlify.com *.beautifyconverter.com *.healthchecks.io *.mgstage.com *.gvt2.com *.gstatic.com *.googleapis.com *.gmodules.com *.sesexiaozhan.com *.biquge.tw *.acgnx.se *.combot.org *.tenor.com *.pixeldrain.com *.gamer.com.tw *.bahamut.com.tw *.seiya-saiga.com *.ptpimg.me *.wiki.gg HostRegex: ^.*\brmcdn\d+\.xyz$ HostRegex: ^.*\brn\d+\.xyz$ HostRegex: ^.*\broum\d+\.xyz$ ; ============================================================================== ; ============================================================================== ; https://github.com/xinhugo/Free-List HostRegex: ^(sp|www|actress|pics)\.dmm\.co\.jp$ www.dmm.com www.r18.com *.1024search.tk *.1080.tw *.1688.com.au *.1dpw.com *.1pondo.tv *.2008xianzhang.info *.24smile.org *.4shared.com *.5i01.com *.5z5.com *.64memo.com *.64tianwang.com *.64wiki.com *.666kb.com *.6do.news *.6park.com *.a1080hd.com *.abc.xyz *.ablwang.com *.aboluowang.com *.actimes.com.au *.adblockplus.org *.adobe.com *.adsafeprotected.com *.adsrvr.org *.adultblogranking.com *.aforcemorepowerful.org *.ahd1080.com *.aida64.com *.aisex.com *.aishangyou.tube *.ait.org.tw *.akamaihd.net *.alabout.com *.alicejapan.co.jp *.aliengu.com *.alliance.org.hk *.allinfa.com *.am730.com.hk *.amazon.co.jp *.amazonaws.com *.amazonwebapps.com *.amnesty.org *.amnesty.tw *.ananass.fr *.android.com *.androidfilehost.com *.animecrazy.net *.anti1984.com *.anygoing.com *.aomiwang.com *.aoqinet.com *.apkmirror.com *.app2.hkatv.com *.appledaily.hk *.appspot.com *.archive.org *.asahichinese.com *.asianews.it *.aszw.com *.atchinese.com *.atdmt.com *.atgfw.org *.aurl.mobi *.ausdaily.net.au *.autoit-cdn.com *.autoitscript.com *.avbbs.tv *.avcity.tv *.avlang.com *.av-scouter.info *.avsp2p.com *.backchina.com *.backtotiananmen.com *.badongo.com *.baisex.me *.bannedbook.org *.bayfiles.net *.bbcchinese.com *.bbg.gov *.bcchinese.net *.beijingspring.com *.bet365.com *.betanews.com *.beyondfirewall.com *.bind9.net *.binux.me *.bit.ly *.bitshare.com *.bitsnoop.com *.biz.tm *.bjs.org *.bjzc.org *.blinkx.com *.blockedinchina.net *.blog.jp *.blog.xuite.net *.blog.yam.com *.blogblog.com *.blogcatalog.com *.blogcity.me *.blogimg.jp *.bloglines.com *.bloglovin.com *.blogs.com *.blogspot.in *.blogspot.kr *.bmvflorida.us *.bod.asia *.book.com.tw *.books.com.tw *.bootstrapcdn.com *.botanwang.com *.botanwang.org *.bowenpress.com *.boxun.com *.boxun.us *.break.com *.brizzly.com *.btdigg.org *.btku.org *.btn.weather.ca *.bud.org.tw *.buff.ly *.bullog.org *.bullogger.com *.businessweek.com *.bwp.im *.bx.tl *.c.bigcache.googleapis.com *.c000.me *.c080.me *.c800.me *.c9x.info *.cahr.org.tw *.campaign.tw-npo.org *.cams.org.sg *.canyu.org *.caochangqing.com *.cap.org.hk *.cari.com.my *.caribbeancom.com *.catholic.org.hk *.catholic.org.tw *.catwizard.net *.cbc.ca *.ccdtr.org *.ccim.org *.ccw.org.tw *.cdef.org *.cdjp.org *.cdnews.com.tw *.cdns.com.tw *.cecc.gov *.cenews.eu *.centralnation.com *.cfhks.org.hk *.cgdepot.org *.chicagoncmtv.com *.china.ucanews.com *.chinaaid.net *.chinabiz.org.tw *.chinadigitaltimes.net *.chinaelections.com *.chinaelections.org *.chinaeweekly.com *.chinafile.com *.chinagfw.org *.chinainperspective.com *.chinapress.com.my *.chinarightsia.org *.chinatcc.gov.cn *.chinatimes.com *.china-week.com *.chinaworker.info *.chinesedaily.com *.chinesenewsnet.com *.chinesepen.org *.chinesetoday.com *.chosun.com *.christianstudy.com *.christiantimes.org.hk *.chrlawyers.hk *.chrome.com *.chromium.org *.chubun.com *.cincainews.com *.citizenlab.org *.citizensradio.org *.city9x.com *.civicparty.hk *.civilhrfront.org *.civilmedia.tw *.ck101.com *.ckcdn.com *.clickme.net *.cloudmonitor.ca.com *.cna.com.tw *.cnd.org *.cnliberals.com *.cnn.com *.cnpolitics.org *.cnyes.com *.codeproject.com *.comefromchina.com *.competitionforce.hk *.cool18.com *.coolloud.org.tw *.cotweet.com *.cpuid.com *.crazys.cc *.creaders.net *.creadersnet.com *.crwdcntrl.net *.crystalmark.info *.c--spanarchives.-org *.c-spanvideo.org *.cts.com.tw *.cw.com.tw *.d100.net *.d2pass.com *.dadazim.com *.dailymotion.com *.dalailamaworld.com *.danwei.org *.daolan.net *.dbanotes.net *.del.icio.us *.democraticchina.org *.democrats.org *.de-sci.org *.dfs.kuaipan.cn *.dfzdaili.com *.dieneueepoche.com *.digg.com *.digitalocean.com *.digitalvolcano.co.uk *.diigo.com *.dipity.com *.discuss.com.hk *.disp.cc *.disqus.com *.djjsq.com *.djorz.com *.dnscrypt.org *.doit.im *.dolc.de *.dolf.org.hk *.dongtaiwang.com *.doub.io *.doubibackup.com *.download.aircrack-ng.org *.dphk.org *.dpp.org.tw *.dropbox.com *.dropboxusercontent.com *.drsunacademy.com *.dtiblog.com *.duga.jp *.duihua.org *.duping.net *.dupola.com *.dw.com *.dw.de *.dw-world.com *.dw-world.de *.dxlive.com *.e123.hk *.ebookbrowse.com *.ecfa.org.tw *.echinanews.com.tw *.edge.liveleak.com *.edicypages.com *.edoors.com *.efcc.org.hk *.eff.org *.eic-av.com *.e-info.org.tw *.elpais.com *.emilylau.org.hk *.erabaru.net *.erights.net *.eroantenna.com *.ero-video.net *.es-visiontimes.com *.etaiwannews.com *.ettoday.net *.evernote.com *.extremetube.com *.extremetube.phncdn.com *.eyny.com *.facebook.com *.facebook.net *.fangeming.com *.fanqiang.network *.fanqianghou.com *.farxian.com *.fastly.net *.faststone.org *.favstar.fm *.faydao.com *.fb.me *.fbcdn.net *.fc2.com *.fdc89.jp *.feedburner.com *.feedjit.com *.feedsportal.com *.felixcat.net *.ffx.io *.filecroco.com *.filesor.com *.file-static.com *.filestube.com *.firebaseio.com *.firepic.org *.flyzy2005.com *.fmnnow.com *.foofind.is *.fooooo.com *.forum.kaiyuan.de *.forum.tvb.com *.fqrouter.com *.free4u.com.ar *.freebrowser.org *.freedomhouse.org *.freeproxyserver.net *.freeshadow.info *.freetufu.com *.freewechat.com *.freeweibo.com *.friendfeed.com *.fring.com *.frontlinedefenders.org *.ftchinese.com *.fukugan.com *.fuli.ba *.fullyillustrated.com *.fungchiwood.com *.funp.com* *.fw.cm *.fw.com *.g.co *.gaeproxy.com *.gamebase.com.tw *.gameclub.tw *.ganges.com *.gcpnews.com *.geocities.co.jp *.getfoxyproxy.org *.getgom.com *.getsync.com *.ggpht.com *.ghd1080.com *.gigacircle.com *.gimp.org *.git.io *.github.com *.github.io *.githubusercontent.com *.git-scm.com *.gittigidiyor.com *.globalvoices.org *.globalvoicesonline.org *.glorystar.me *.gmail.com *.gnews.org *.goagent.biz *.goo.gl *.google *.google.com *.googlecode.com *.googlesource.com *.googletagmanager.com *.googleusercontent.com *.googlevideo.com *.gopetition.com *.gospelherald.com *.gospelherald.com.hk *.gov.tw *.gpass1.com *.gravatar.com *.greatfire.org *.greatfirewallofchina.org *.greatzhonghua.org *.greenparty.org.tw *.gvm.com.tw *.h528.com *.haixiainfo.com.tw *.hakkatv.org.tw *.hav.tv *.have8.com *.h-china.org *.hcocoa.com *.hd1080.org *.hdtransform.com *.hechaji.com *.helpzhuling.org *.heqinglian.net *.hetnieuwetijdperk.com *.hexieshe.com *.hikinggfw.org *.hilive.tv *.hioz.org *.hitwister.com *.hjav.org *.hjclub.info *.hk01.com *.hkatvnews.com *.hkchurch.org *.hkci.org.hk *.hkcnews.com *.hkcrm.org.hk *.hkdailynews.com.hk *.hkdash.com *.hkej.com *.hket.com *.hketgroup.com *.hkgolden.com *.hkgoldenmobile.com *.hkhkhk.com *.hkhrm.org.hk *.hkja.org.hk *.hkjc.com *.hkjp.org *.hkptu.org *.hk-pub.com *.hkreporter.com *.hksilicon.com *.hkumall.com *.hkupop.hku.hk *.hkusu.org *.hkwcc.org.hk *.hongkongfp.com *.hongkongtibetfilmfestival2015.com *.hotchyx.com *.hotspotshield.com *.hrichina.org *.hrw.org *.huanghuagang.org *.huaren.us *.huaxia-news.com *.huping.net *.hutong9.net *.hwinfo.com *.hxmmdd.com *.hyperrate.com *.hzy.pw *.i1.hk *.i2ocr.com *.i2p2.de *.iask.ca *.iceimg.com *.icij.org *.idol-mile.com *.idv.tw *.ifanqiang.com *.ift.tt *.igfw.??? *.igfw.tk *.igossip.com *.ihao.org *.ihd1080.org *.ihktv.com *.imagebam.com *.imageshack.us *.imagestorming.com *.imageurlhost.com *.imagevenue.com *.imagezilla.net *.img.ly *.imgburn.com *.imgchili.com *.imgchili.net *.imgdino.com *.imgkeep.com *.imgly.net *.imgtiger.com *.imgur.com *.immoral.jp *.i-mobile.co.jp *.inmediahk.net *.inote.tw *.inside.com.tw *.instagram.com *.insynchq.com *.intermargins.net *.internet.org *.internetfreedom.org *.inxian.com *.ipcf.org.tw *.ipicture.ru *.ipkmedia.com *.isohunt.com *.isunaffairs.com *.isuntv.com *.ithelp.ithome.com.tw *.ixxx.com *.iyouport.com *.iyouport.org *.j.mp *.jasonsavard.com *.jav008.com *.javblog.biz *.javfree.me *.javideo.info *.javsharing.com *.jayxon.com *.jbtalks.cc *.jinbushe.org *.jingfeng.info *.jingpin.org *.jiruan.net *.jjgirls.com *.jkforum.net *.jpavgod.com *.justin.tv *.just-ping.com *.kan.center *.kankan.today *.keakon.net *.keephkshining.com *.kenengba.com *.kexueshangwang.info *.kinghost.com *.kingstone.com.tw *.kir.jp *.kwongwah.com.my *.la-forum.org *.lagranepoca.com *.lailaibt.com *.laqingdan.net *.latteye.com *.lcx.cc *.lefora.com *.left21.hk *.lemonde.fr *.lesoir.be *.letscorp.net *.libertytimes.com.tw *.libreoffice.org *.life.com.tw *.limbopro.xyz *.lineageosrom.com *.line-scdn.net *.linkbucks.com *.listhub.net *.liuxiaobo.net *.liveleak.com *.livestation.com *.livestream.com *.localpresshk.com *.lockerz.com *.lolbin.net *.loli.net *.longhair.hk *.lookpic.com *.loved.hk *.lrip.org *.lsd.org.hk *.lsj1080.cc *.lsj1080.net *.lsj1080.tv *.ltn.com.tw *.lvv2.com *.mail-archive.com *.maiplus.com *.malaymail.com *.malaysiakini.com *.matters.news *.mattwilcox.net *.medium.com *.mefeedia.com *.memehk.com *.merit-times.com *.merit-times.com.tw *.merlinblog.xyz *.metrolife.ca *.metroradio.com.hk *.microad.jp *.minghui.org *.mingjinglishi.com *.mingjingnews.com *.mingpaonews.com *.mingshengbao.com *.minnano-av.com *.minus.com *.minzhuzhongguo.org *.mirrorbooks.com *.mitbbs.com *.mkini.net *.mlxiaoshuo.com *.mobile01.com *.mobileways.de *.mobypicture.com *.moedict.tw *.mojim.com *.mokeedev.com *.molihua.org *.mono.ac *.mono.sh *.moonbbs.com *.mozilla.net *.mp3ye.eu *.mpfinance.com *.msguancha.com *.mtlmp4.com *.myca168.com *.mychinanews.com *.mycnnews.com *.mycould.com *.myfreecams.com *.myfreshnet.com *.myhd1080.tv *.myradio.com.hk *.myradio.hk *.mysinablog.com *.nanyang.com *.nanyangpost.com *.ncchinesenews.com *.ndr.de *.net1.hkbu.edu.hk *.netgear.com *.netme.cc *.network54.com *.networkedblogs.com *.newcenturymc.com *.newcenturynews.com *.newhighlandvision.com *.news.hk.msn.com *.news.pts.org.tw *.newsancai.com *.newstapa.org *.newtaiwan.com.tw *.newtalk.tw *.nextdigital.com.hk *.nextmag.com.tw *.nextmedia.com *.ngensis.com *.nicovideo.jp *.nobel.se *.nobelprize.org *.notepad-plus-plus.org *.notipage.com *.nownews.com *.nps.gov *.nrk.no *.ntd.tv *.ntdtv.com *.ntdtv-dc.com *.nuzcom.com *.nvquan.org *.nyt.com *.nytcn.me *.nyti.ms *.nytimes.com *.nytimg.com *.nytstyle.com *.observechina.net *.oclp.hk *.ogaoga.org *.oiktv.com *.olx.com.br *.on.cc *.onedrive.live.com *.onlinestuffs.com *.open.com.hk *.opendemocracy.net *.openstreetmap.org *.orientaldaily.com.my *.orientaldaily.on.cc *.orzhd.com *.oursogo.com *.oursteps.com.au *.outbrain.com *.ow.ly *.oyax.com *.oyou.com.au *.page2rss.com *.pageflakes.com *.panoramio.com *.pao-pao.net *.paper.li *.passiontimes.hk *.pbwiki.com *.pbworks.com *.pcdvd.com.tw *.pchome.com.tw *.pcij.org *.peacehall.com *.penchinese.org *.peopo.org *.perfspot.com *.pfd.org.hk *.picpar.com *.picrar.com *.piebridge.me *.pimgs.com *.pincong.rocks *.ping.fm *.pinimg.com *.pinterest.com *.piratescreen.com *.piring.com *.pixshock.net *.playno1.com *.playno1.com.tw *.popvote.hk *.popyard.com *.popyard.org *.porn.com *.pornhub.com *.post852.com *.posterous.com *.potatso.com *.potatsocontent.com *.powerlinks.com *.premeforwindows.com *.prestige-av.com *.prettyvirgin.com *.privoxy.org *.pubu.com.tw *.qidian.ca *.qiwen.lu *.quickpornsearch.com *.quora.com *.quoracdn.net *.qxbbs.org *.radioaustralia.net.au *.ranyunfei.com *.rapbull.net *.rarbg.to *.rcinet.ca *.readingtimes.com.tw *.rebecca-web.com *.recovery.org.tw *.redchinacn.org *.reddit.com *.redsquirrel87.com *.redtube.com *.reduik.com *.referer.us *.relink.us *.rendsmap.com *.renminbao.com *.resilio.com *.restorehk.com *.reuters.com *.reutersmedia.net *.rfa.org *.rferl.org *.rfi.fr *.rfi.my *.rghost.net *.riku.me *.rmjdw.com *.rnw.nl *.rocmp.org *.roodo.com *.rsf.org *.rsf-chinese.org *.rthk.hk *.rthk.org.hk *.rti.org.tw *.s3.amazonaws.com *.sadpanda.us *.sanmin.com.tw *.sanminjiaoliu.net *.savemedia.com *.savetube.com *.savevid.com *.saveyoutube.com *.scdn.co *.scholarism.com *.s-dragon.org *.search.xxx *.secretchina.com *.securityinabox.org *.securitykiss.com *.sendspace.com *.setn.com *.sex.com *.sexinsex.net *.shadow.ma *.shadowgov.tw *.shadowsocks.org *.sharenxs.com *.sharpdaily.com *.sharpdaily.tw *.sherrychan.net *.shizhao.org *.shr.lc *.shutterstock.com *.sinaapp.co *.singtao.com *.sinomontreal.ca *.sinoquebec.com *.sis001.com *.sitebro.tw *.six-degrees.io *.slideshare.net *.smh.com.au *.smhric.org *.soduso.com *.softether-download.com *.sohcradio.com *.sondelespoir.org *.sonidodelaesperanza.org *.sougouwiki.com *.soundofhope.co.kr *.soundofhope.kr *.soundofhope.org *.soup-dev.com *.sourceforge.net *.southnews.com.tw *.spankwire.com *.spotify.com *.spring4u.info *.ssr.tools *.startpage.com *.static-file.com *.steemit.com *.stgloballink.com *.stickeraction.com *.stimme-de.de *.stooorage.com *.storify.com *.storm.mg *.stormmediagroup.com *.strongvpn.com *.stumbleupon.com *.stupidvideos.com *.sucuri.net *.sugarsync.com *.sun1911.com *.supersu.com *.swagbucks.com *.sydneytoday.com *.t.co *.t.me *.t66y.com *.taaze.tw *.tahr.org.tw *.taiwandaily.net *.taiwanus.net *.talkonly.net *.tampermonkey.net *.tantannews.com *.tavis.tw *.tca.org.tw *.tdesktop.com *.techinasia.com *.technorati.com *.telegra.ph *.telegram.me *.telegram.org *.telesco.pe *.tenacy.com *.tenacy-free.com *.theblaze.com *.thebobs.com *.theepochtimes.com *.thefrontier.hk *.theglobalmail.org *.theguardian.com *.thehousenews.com *.theinitium.com *.thenewslens.com *.the-sun.on.cc *.thetibetpost.com *.thetimenow.com *.thinkingtaiwan.com *.thisav.com *.tiananmenduizhi.com *.tiananmenmother.org *.tiananmenuniv.com *.tiananmenuniv.net *.tibet.net *.time.com *.timer-tab.com *.tiny.cc *.tinychat.com *.tmagazine.com *.tmu.edu.tw *.tokyocn.com *.topcoo.com *.topsy.com *.torrentcrazy.com *.torrentkitty.com *.trackon.org *.transformativeworks.org *.transparency.org *.trendsmap.com *.trib.al *.trouw.nl *.trtc.com.tw *.trueimages.ru *.truveo.com *.tsquare.tv *.tsu.org.tw *.tube8.com *.tuiyun.net *.tumblr.com *.tv.com *.tvboxnow.com *.twbbs.org *.twbbs.tw *.tweetdeck.com *.tweettunnel.com *.tweez.net *.twgreatdaily.com *.twicsy.com *.twilk.com *.twimg.com *.twipple.jp *.twitchtv.com *.twitiq.com *.twitpic.com *.twitter.com *.twitterfeed.com *.twittergadget.com *.twitterknowhow.com *.twitvid.com *.twitvid.com.edgesuite.net *.twiyia.com *.twtrland.com *.ucdc1998.org *.udn.com *.udn.com.tw *.ugo.com *.uhrp.org *.uighurbiz.net *.ulifestyle.com.hk *.ultrareach.com *.unblock.cn.com *.uncyclopedia.tw *.upmedia.mg *.uproxy.org *.ustream.tv *.utorrent.com *.uwants.com *.uyghuramerican.org *.uyghurpress.com *.uyl.me *.v2ex.com *.van698.com *.vaticanradio.org *.vdonext.com *.velkaepocha.sk *.venchina.com *.veoh.com *.vevo.com *.viber.com *.video.pbs.org *.vietdaikynguyen.com *.vinmusic.com *.visiontimes.ca *.vjmedia.com.hk *.vocus.cc *.vot.org *.vox-cdn.com *.vpngate.jp *.vpngate.net *.vpnxunlu.com *.wahas.com *.wanglixiong.com *.want-daily.com *.watchzerg.com *.watorrent.org *.wattpad.com *.wdf5.com *.wearn.com *.web.pts.org.tw *.weblagu.com *.websitepulse.com *.webwarper.net *.wegfw.com *.weibosuite.com *.weijingsheng.org *.weiquanwang.org *.wengewang.org *.wenxuecity.com *.westca.com *.westkit.net *.wetransfer.com *.whattalking.com *.whogovernstw.org *.whotalking.com *.wi-gadget.com *.wikia.com *.wikia.net *.wikia.nocookie.net *.wikia-beacon.com *.wikileaks.org *.wikilivres.ca *.wiztechnologies.jp *.wn.com *.woolyss.com *.worldcat.org *.worldjournal.com *.wp.com *.wp.me *.wrchina.org *.wretch.cc *.wuala.com *.wuerkaixi.com *.wujie.net *.wujieliulan.com *.wwitv.com *.xanga.com *.xbookcn.net *.xhamster.com *.xiaochuncnjp.com *.xinmiao.com.hk *.xinyubbs.net *.xiti.com *.xizang-zhiye.org *.xjp.cc *.xn--fiq681d48s.org *.xpics.us *.xtube.com *.xuehua.us *.xunluvpn.com *.xvideos.com *.xvideos-field5.com *.xvideosmatome.net *.xys.org *.yamedia.tw *.yasni.co.uk *.ycp.hk *.yfrog.com *.yhritw.org *.yibaochina.com *.yidio.com *.yorkbbs.ca *.you2go.me *.youjizz.com *.youmaker.com *.youporn.com *.youthwant.com.tw *.youtu.be *.youtube.com *.youtubeinmp3.com *.yowindow.com *.ytimg.com *.yzzk.com *.zhengjian.org *.zhenlibu1984.com *.zhongzilou.com *.zhoushuguang.com *.zinio.com *.zlvc.net *.zlvc.net.he2.aqb.so *.zomobo.net *.zorpia.com *.zuo.la *.zuobiao.me *.zuola.com *.zxing.org *.zyzc.greatzhonghua.org .cdninstagram.com .myhd1080.com \bhttp://(.[^/])*\.*\bdelicious\.com/ ^http://search\.aol\.com/ 184.154.128.246 75.119.209.87 ad.dmm.com addons-amo.cdn.mozilla.net bbs.51.ca bufferapp.com c.statcounter.com cdn.media.abc.com cdn.rawgit.com cdn.viafoura.net cdns.gigya.com chinese.engadget.com community.emc.com dl.xda-developers.com download.documentfoundation.org download.lineageos.org download.tuxfamily.org downloadap1.teamviewer.com download-cdn.resilio.com forum.gamer.com.tw forum.games.hinet.net fpdownload.macromedia.com ftp.mozilla.org hcd-1.imgbox.com hk03dl.com HostRegex: ^((.+\.c\.doc-0-0-sj\.|)sj|ci\d{1,2}|oauth|webcache|\d-ps|uds|producer|lh\d|s\d|.+-opensocial|clients\d|translate|mail-attachment|.+-docs)\.googleusercontent\.com$ HostRegex: ^((id|scholar|news|accounts|adwords|books|images|khms\d|www|blogsearch|cse|encrypted|m|translate)\.|)google\.(^|com\.af|com\.ag|com\.ai|co\.ao|com\.ar|com\.au|com\.bd|com\.bh|com\.bn|com\.bo|com\.br|co\.bw|com\.bz|co\.ck|com\.co|co\.cr|com\.cu|com\.cy|com\.do|com\.ec|com\.eg|com\.et|com\.fj|com\.gh|com\.gi|com\.gt|com\.hk|co\.id|co\.il|co\.in|com\.jm|co\.jp|co\.ke|com\.kh|co\.kr|com\.kw|com\.lb|co\.ls|com\.ly|co\.ma|com\.mm|com\.mt|com\.mx|com\.my|co\.mz|com\.na|com\.nf|com\.ng|com\.ni|com\.np|co\.nz|com\.om|com\.pa|com\.pe|com\.pg|com\.ph|com\.pk|com\.pr|com\.py|com\.qa|com\.sa|com\.sb|com\.sg|com\.sl|com\.sv|co\.th|com\.tj|com\.tr|com\.tw|co\.tz|com\.ua|co\.ug|co\.uk|com\.uy|co\.uz|com\.vc|co\.ve|co\.vi|com\.vn|co\.za|co\.zm|co\.zw|cat|ad|ae|al|am|as|at|az|ba|be|bf|bg|bi|bj|bs|bt|by|ca|cd|cf|cg|ch|ci|cl|cm|com|cv|cz|de|dj|dk|dm|dz|ee|es|fi|fm|fr|ga|ge|gg|gl|gm|gp|gr|gy|hn|hr|ht|hu|ie|im|iq|is|it|je|jo|kg|ki|kz|la|li|lk|lt|lu|lv|md|me|mg|mk|ml|mn|ms|mu|mv|mw|ne|nl|no|nr|nu|pl|pn|ps|pt|ro|rs|ru|rw|sc|se|sh|si|sk|sm|sn|so|sr|st|td|tg|tk|tl|tm|tn|to|tt|vg|vu|ws|jp|tw|hk)$ HostRegex: ^((uploads\.|)code(|\.l)|.*\bdocs|doc|.*\bdrive|play|plus|plus\.url|buzz|profiles|mail|apis|support|wallet|checkout|talkgadget|appengine|store|security|myaccount|chrome|contacts|safebrowsing(|-cache|\.clients)|calendar|clients\d|mt(|s)\d|picasa\w*|sites|fusion|reader|feed\w*|input|research|inbox|mw\d|gg|peering|events|fit|tools|music|on|photo(|s))\.google\.com$ HostRegex: ^(daily|)news\.sina\.com(\.(hk|tw)|)$ HostRegex: ^(feedproxy|feedburner|hangouts|keep)\.google\.com$ HostRegex: ^(maps|www|translate|plus|ajax|mts\d|commondatastorage|chart|storage|fonts)\.googleapis\.com$ HostRegex: ^(skins|www(|\.ig))\.gmodules\.com$ HostRegex: ^(space|bbs)\.qoos\.com$ HostRegex: ^.*\b(blogger|blogspot)\.(^|com\.af|com\.ag|com\.ai|co\.ao|com\.ar|com\.au|com\.bd|com\.bh|com\.bn|com\.bo|com\.br|co\.bw|com\.bz|co\.ck|com\.co|co\.cr|com\.cu|com\.cy|com\.do|com\.ec|com\.eg|com\.et|com\.fj|com\.gh|com\.gi|com\.gt|com\.hk|co\.id|co\.il|co\.in|com\.jm|co\.jp|co\.ke|com\.kh|co\.kr|com\.kw|com\.lb|co\.ls|com\.ly|co\.ma|com\.mm|com\.mt|com\.mx|com\.my|co\.mz|com\.na|com\.nf|com\.ng|com\.ni|com\.np|co\.nz|com\.om|com\.pa|com\.pe|com\.pg|com\.ph|com\.pk|com\.pr|com\.py|com\.qa|com\.sa|com\.sb|com\.sg|com\.sl|com\.sv|co\.th|com\.tj|com\.tr|com\.tw|co\.tz|com\.ua|co\.ug|co\.uk|com\.uy|co\.uz|com\.vc|co\.ve|co\.vi|com\.vn|co\.za|co\.zm|co\.zw|cat|ad|ae|al|am|as|at|az|ba|be|bf|bg|bi|bj|bs|bt|by|ca|cd|cf|cg|ch|ci|cl|cm|com|cv|cz|de|dj|dk|dm|dz|ee|es|fi|fm|fr|ga|ge|gg|gl|gm|gp|gr|gy|hn|hr|ht|hu|ie|im|iq|is|it|je|jo|kg|ki|kz|la|li|lk|lt|lu|lv|md|me|mg|mk|ml|mn|ms|mu|mv|mw|ne|nl|no|nr|nu|pl|pn|ps|pt|ro|rs|ru|rw|sc|se|sh|si|sk|sm|sn|so|sr|st|td|tg|tk|tl|tm|tn|to|tt|vg|vu|ws|jp|tw|hk|sg)$ HostRegex: ^.*\b(scmp(chinese|)|nanzao)\.com$ HostRegex: ^.*\balanleong\.(net|com)$ HostRegex: ^.*\bappledaily\.com(|\.(tw|hk))$ HostRegex: ^.*\bavlang\d+\.com$ HostRegex: ^.*\bbbc(i|)\.co\.uk$ HostRegex: ^.*\bbbc\.(com|in)$ HostRegex: ^.*\bbeacons(|\d)\.gvt2\.com$ HostRegex: ^.*\bbloomberg\.(com|(i|c)n)$ HostRegex: ^.*\bcdp(|2006|wu|site|1998)\.org$ HostRegex: ^.*\bdwnews\.(com|net(:\d+|))$ HostRegex: ^.*\bepoch(times(-romania|tr|-bg)|tw|hk)\.com$ HostRegex: ^.*\bepochtimes\.(com\.(tw|hk|ua)|com|fr|de|co\.(il|kr)|jp|ru|it|se)$ HostRegex: ^.*\bg(nci|cc)\.org\.hk$ HostRegex: ^.*\bgardennetworks\.(org|com)$ HostRegex: ^.*\bgreatroc\.(tw|org)$ HostRegex: ^.*\bkagoya\.(net|jp)$ HostRegex: ^.*\bkanzhongguo\.(com(\.au|)|eu)$ HostRegex: ^.*\bmingpao(|ny|tor|van|canada|news|monthly)\.com$ HostRegex: ^.*\bnext(tv|vod)\.com\.tw$ HostRegex: ^.*\bntdtv(|-dc)\.com$ HostRegex: ^.*\bthepiratebay\.(s(e|x)|org)$ HostRegex: ^.*\bthywords\.com(|\.tw)$ HostRegex: ^.*\bvoa(fanti|chinese|cantonese|tibetan(|english))\.com$ HostRegex: ^.*\bvoanews\.(com|eu)$ HostRegex: ^.*\bwsj\.(com|net)$ HostRegex: ^.*\byoutube\.(^|com\.af|com\.ag|com\.ai|co\.ao|com\.ar|com\.au|com\.bd|com\.bh|com\.bn|com\.bo|com\.br|co\.bw|com\.bz|co\.ck|com\.co|co\.cr|com\.cu|com\.cy|com\.do|com\.ec|com\.eg|com\.et|com\.fj|com\.gh|com\.gi|com\.gt|com\.hk|co\.id|co\.il|co\.in|com\.jm|co\.jp|co\.ke|com\.kh|co\.kr|com\.kw|com\.lb|co\.ls|com\.ly|co\.ma|com\.mm|com\.mt|com\.mx|com\.my|co\.mz|com\.na|com\.nf|com\.ng|com\.ni|com\.np|co\.nz|com\.om|com\.pa|com\.pe|com\.pg|com\.ph|com\.pk|com\.pr|com\.py|com\.qa|com\.sa|com\.sb|com\.sg|com\.sl|com\.sv|co\.th|com\.tj|com\.tr|com\.tw|co\.tz|com\.ua|co\.ug|co\.uk|com\.uy|co\.uz|com\.vc|co\.ve|co\.vi|com\.vn|co\.za|co\.zm|co\.zw|cat|ad|ae|al|am|as|at|az|ba|be|bf|bg|bi|bj|bs|bt|by|ca|cd|cf|cg|ch|ci|cl|cm|cn|com|cv|cz|de|dj|dk|dm|dz|ee|es|fi|fm|fr|ga|ge|gg|gl|gm|gp|gr|gy|hn|hr|ht|hu|ie|im|iq|is|it|je|jo|kg|ki|kz|la|li|lk|lt|lu|lv|md|me|mg|mk|ml|mn|ms|mu|mv|mw|ne|nl|no|nr|nu|pl|pn|ps|pt|ro|rs|ru|rw|sc|se|sh|si|sk|sm|sn|so|sr|st|td|tg|tk|tl|tm|tn|to|tt|vg|vu|ws|jp|tw|hk)$ HostRegex: ^38\.103\.161\.\d{1,3}$ images.secdns.com international.sueddeutsche.de irs0.4sqi.net libs.pixfs.net mirrorbits.lineageos.org pds.nasa.gov pics.dmm.co.jp plaza.jp.rakuten-static.com s1.pir.fm sendtokindle.amazon.cn support.mozilla.org times.hinet.net torrent.ubuntu.com tweets.seraph.me twimg.edgesuite.net UrlRegex: ^http(|s)://(.[^/])*\.*\bak\.live\.cntv\.cn/z/ UrlRegex: ^http(|s)://\d+\.client-channel\.google\.com/client-channel/js/ UrlRegex: ^http(|s)://windows\.microsoft\.com/.*/windows/themes UrlRegex: ^http://(.[^/])*\.*\b(home|forum)\.gamer\.com\.tw UrlRegex: ^http://(.[^/])*\.*\b(news|blog)\.cnyes\.com/ UrlRegex: ^http://(.[^/])*\.*\b(news|popblog)\.tvbs\.com\.tw/ UrlRegex: ^http://(.[^/])*\.*\b(showbiz|forum|news|cling)\.omy\.sg/ UrlRegex: ^http://(.[^/])*\.*\b(trans|blog)wenweipo\.com/ UrlRegex: ^http://(.[^/])*\.*\b2o7\.net/ UrlRegex: ^http://(.[^/])*\.*\b881903\.com/Page/ZH-TW/index UrlRegex: ^http://(.[^/])*\.*\ba164\.edgecastcdn\.net/ UrlRegex: ^http://(.[^/])*\.*\badsbro\.com/ UrlRegex: ^http://(.[^/])*\.*\baolnews\.com/ UrlRegex: ^http://(.[^/])*\.*\bapi\.i\.pixnet\.cc/ UrlRegex: ^http://(.[^/])*\.*\barchive\.is/ UrlRegex: ^http://(.[^/])*\.*\barchive\.org/ UrlRegex: ^http://(.[^/])*\.*\bavast\.com/ UrlRegex: ^http://(.[^/])*\.*\bbitly\.com/ UrlRegex: ^http://(.[^/])*\.*\bbitsnoop\.com/ UrlRegex: ^http://(.[^/])*\.*\bblog\.livedoor\.jp/ UrlRegex: ^http://(.[^/])*\.*\bblogsys\.jp/ UrlRegex: ^http://(.[^/])*\.*\bbluekai.com/ UrlRegex: ^http://(.[^/])*\.*\bbtdigg.org/ UrlRegex: ^http://(.[^/])*\.*\bbuzzhand\.com/ UrlRegex: ^http://(.[^/])*\.*\bcable(v|news)\.i-cable\.com/ UrlRegex: ^http://(.[^/])*\.*\bchange\.org/ UrlRegex: ^http://(.[^/])*\.*\bchapm25\.com/ UrlRegex: ^http://(.[^/])*\.*\bchartbeat\.net/ UrlRegex: ^http://(.[^/])*\.*\bcloudflare\.com/ UrlRegex: ^http://(.[^/])*\.*\bcloudfront\.net/ UrlRegex: ^http://(.[^/])*\.*\bclustrmaps.com/ UrlRegex: ^http://(.[^/])*\.*\bcrwdcntrl\.net/ UrlRegex: ^http://(.[^/])*\.*\bcxense.com/ UrlRegex: ^http://(.[^/])*\.*\bdisqus\.com/ UrlRegex: ^http://(.[^/])*\.*\bdowjoneson.com/ UrlRegex: ^http://(.[^/])*\.*\bdropbox\.com/ UrlRegex: ^http://(.[^/])*\.*\bdropboxusercontent\.com/ UrlRegex: ^http://(.[^/])*\.*\bduckduckgo\.com/ UrlRegex: ^http://(.[^/])*\.*\bdxtl\.net/ UrlRegex: ^http://(.[^/])*\.*\bdynamicyield\.com/ UrlRegex: ^http://(.[^/])*\.*\bensighten\.com/ UrlRegex: ^http://(.[^/])*\.*\beslite\.com/product UrlRegex: ^http://(.[^/])*\.*\bfeedly\.com/ UrlRegex: ^http://(.[^/])*\.*\bflip\.it/ UrlRegex: ^http://(.[^/])*\.*\bfout.jp/ UrlRegex: ^http://(.[^/])*\.*\bgetemoji\.com/cdn-cgi/pe/ UrlRegex: ^http://(.[^/])*\.*\bgetpocket\.com/ UrlRegex: ^http://(.[^/])*\.*\bgooglesyndication\.com/ UrlRegex: ^http://(.[^/])*\.*\bhaxx\.se/ UrlRegex: ^http://(.[^/])*\.*\bhkxforce\.net/wordpress/ UrlRegex: ^http://(.[^/])*\.*\bhootsuite\.com/ UrlRegex: ^http://(.[^/])*\.*\bimage\.blozoo\.info/ UrlRegex: ^http://(.[^/])*\.*\bimgur\.com/ UrlRegex: ^http://(.[^/])*\.*\bimrworldwide.com/ UrlRegex: ^http://(.[^/])*\.*\binstagram\.com/ UrlRegex: ^http://(.[^/])*\.*\bkakao\.com/ UrlRegex: ^http://(.[^/])*\.*\bkrxd\.net/ UrlRegex: ^http://(.[^/])*\.*\bksnews\.com\.tw/ UrlRegex: ^http://(.[^/])*\.*\blijit\.com/ UrlRegex: ^http://(.[^/])*\.*\bline\.me/ UrlRegex: ^http://(.[^/])*\.*\blongurl\.org/ UrlRegex: ^http://(.[^/])*\.*\bmassrelevance\.com/ UrlRegex: ^http://(.[^/])*\.*\bmediafire\.com/ UrlRegex: ^http://(.[^/])*\.*\bmediawiki\.org/ UrlRegex: ^http://(.[^/])*\.*\bmetrohk\.com\.hk/\?cmd=detail&categoryID=2 UrlRegex: ^http://(.[^/])*\.*\bmoby\.to/ UrlRegex: ^http://(.[^/])*\.*\bmoneydj\.com/(KMDJ|kmdj) UrlRegex: ^http://(.[^/])*\.*\bmyhd1080\.com/ UrlRegex: ^http://(.[^/])*\.*\bnetsh\.org/ UrlRegex: ^http://(.[^/])*\.*\bnews\.singtao\.ca/ UrlRegex: ^http://(.[^/])*\.*\bnews\.tvb\.com/(local|list/world) UrlRegex: ^http://(.[^/])*\.*\bnyaa\.se/ UrlRegex: ^http://(.[^/])*\.*\bomtrdc\.net/ UrlRegex: ^http://(.[^/])*\.*\boo-software\.com/ UrlRegex: ^http://(.[^/])*\.*\bopendns\.com/ UrlRegex: ^http://(.[^/])*\.*\bopenx\.net/ UrlRegex: ^http://(.[^/])*\.*\boutbrain\.com/ UrlRegex: ^http://(.[^/])*\.*\bparsely\.com/ UrlRegex: ^http://(.[^/])*\.*\bpastebin\.com/ UrlRegex: ^http://(.[^/])*\.*\bpastie\.org/ UrlRegex: ^http://(.[^/])*\.*\bpixnet\.net/ UrlRegex: ^http://(.[^/])*\.*\bpo\.st/ UrlRegex: ^http://(.[^/])*\.*\bprisacom\.com/ UrlRegex: ^http://(.[^/])*\.*\bpsiphon\.ca/ UrlRegex: ^http://(.[^/])*\.*\bpsiphon3\.com/ UrlRegex: ^http://(.[^/])*\.*\bptt\.cc/ UrlRegex: ^http://(.[^/])*\.*\bpuffstore\.com/ UrlRegex: ^http://(.[^/])*\.*\bquantserve\.com/ UrlRegex: ^http://(.[^/])*\.*\breferer\.pixplug\.in/ UrlRegex: ^http://(.[^/])*\.*\brevsci\.net/ UrlRegex: ^http://(.[^/])*\.*\bscribd\.com/ UrlRegex: ^http://(.[^/])*\.*\bsharethis\.com/ UrlRegex: ^http://(.[^/])*\.*\bsinchew(|-i)\.com(|\.my)/node UrlRegex: ^http://(.[^/])*\.*\bsitemeter\.com/ UrlRegex: ^http://(.[^/])*\.*\bsmarturl\.it/ UrlRegex: ^http://(.[^/])*\.*\bstarboard-pro\.com/ UrlRegex: ^http://(.[^/])*\.*\bstatic-file\.com/shared/upload/video/.*0604 UrlRegex: ^http://(.[^/])*\.*\btalkboxapp.com/ UrlRegex: ^http://(.[^/])*\.*\btenmax\.io/ UrlRegex: ^http://(.[^/])*\.*\bthestandnews\.com UrlRegex: ^http://(.[^/])*\.*\btokyo-hot\.com/ UrlRegex: ^http://(.[^/])*\.*\btorproject\.org/ UrlRegex: ^http://(.[^/])*\.*\btorrentproject.se/ UrlRegex: ^http://(.[^/])*\.*\btorrentz\.eu/ UrlRegex: ^http://(.[^/])*\.*\btraffic\.alexa\.com/ UrlRegex: ^http://(.[^/])*\.*\btumblr\.com UrlRegex: ^http://(.[^/])*\.*\btumutanzi\.com/ UrlRegex: ^http://(.[^/])*\.*\btynt.com/ UrlRegex: ^http://(.[^/])*\.*\budnbkk\.com/(article|bbs) UrlRegex: ^http://(.[^/])*\.*\bus\d+\.gigya\.com/ UrlRegex: ^http://(.[^/])*\.*\bvideo\.ap\.org/ UrlRegex: ^http://(.[^/])*\.*\bvimeo\.com/ UrlRegex: ^http://(.[^/])*\.*\bviss\.me/ UrlRegex: ^http://(.[^/])*\.*\bvisualrevenue\.com/ UrlRegex: ^http://(.[^/])*\.*\bwebtrendslive\.com/ UrlRegex: ^http://(.[^/])*\.*\bwhoer\.net/check\?host= UrlRegex: ^http://(.[^/])*\.*\bwiki(source|quote|books|news|voyage|versity|data|media|pedia)\.org/ UrlRegex: ^http://(.[^/])*\.*\bwikimediafoundation\.org/ UrlRegex: ^http://(.[^/])*\.*\bwiktionary\.org/ UrlRegex: ^http://(.[^/])*\.*\bwoopra\.com/ UrlRegex: ^http://(.[^/])*\.*\bwordpress\.com/ UrlRegex: ^http://(.[^/])*\.*\bwzyboy\.im/ UrlRegex: ^http://(.[^/])*\.*\bxing\.com/ UrlRegex: ^http://(.[^/])*\.*\byahoo\.(com|com\.(hk|tw)|co\.jp)/ UrlRegex: ^http://(.[^/])*\.*\byandex\.ru/ UrlRegex: ^http://(.[^/])*\.*\byoutu\.be/ UrlRegex: ^http://(.[^/])*\.*\bzedo\.com/asw/ UrlRegex: ^http://(library|mjlsh)\.usc\.cuhk\.edu\.hk/ UrlRegex: ^http://66\.147\.244\.112/~hchinaor/ UrlRegex: ^http://67\.220\.92\.\d+/ UrlRegex: ^http://ad\.tagtoo\.co/ UrlRegex: ^http://adadvisor\.net/ UrlRegex: ^http://ads\.contextweb\.com/TagPublish/ UrlRegex: ^http://adsense\.scupio\.com/ADPInline/ UrlRegex: ^http://api\.microsofttranslator\.com/ UrlRegex: ^http://api-public\.addthis\.com/url/ UrlRegex: ^http://b\.scorecardresearch\.com/ UrlRegex: ^http://bbs\.cantonese\.asia/ UrlRegex: ^http://bdaz\.adsfactor\.net/ UrlRegex: ^http://bgp\.he\.net/ UrlRegex: ^http://bs\.serving-sys\.com/BurstingPipe/ UrlRegex: ^http://ca\.pcrookie\.net/blog_images/ UrlRegex: ^http://cdn\.unwire\.hk/wp-content/uploads/.*(WordPress|www\.youtube\.com) UrlRegex: ^http://chinese\.irib\.ir/index\.php/2010-07-25-10-42-26/ UrlRegex: ^http://citytalk\.tw/event UrlRegex: ^http://cn\.last\.fm/facebook/ UrlRegex: ^http://cpms\.now\.com/cpms/ UrlRegex: ^http://data\.inskinmedia\.com/trackports/rep/base/ UrlRegex: ^http://dmp\.doublemax\.net/ UrlRegex: ^http://edigitalsurvey\.com/ UrlRegex: ^http://e-zone\.com\.hk/discuz UrlRegex: ^http://fileserve\.com/file UrlRegex: ^http://findbook\.tw/book UrlRegex: ^http://forum\.xinbao\.de/forum UrlRegex: ^http://hkupop\.hku\.hk/chinese/ UrlRegex: ^http://humanities\.uchicago\.edu/faculty/ywang/history UrlRegex: ^http://img\.readitlater\.com/i/ UrlRegex: ^http://news\.now\.com/home UrlRegex: ^http://news\.tagtoo\.co/site_media/plugin/nownews_tagtoo_plugin\.js UrlRegex: ^http://p\.typekit\.net/ UrlRegex: ^http://r\.skimresources\.com/api/ UrlRegex: ^http://rapidgator\.net/file/ UrlRegex: ^http://seehua\.com/node/ UrlRegex: ^http://servedby\.flashtalking\.com/ UrlRegex: ^http://tas-hk\.toboads\.com/js/ UrlRegex: ^http://track\.tagtoo\.co/ UrlRegex: ^http://u\.scupio\.com/ UrlRegex: ^http://v?\.moatads\.com/ UrlRegex: ^http://wiki\.moegirl\.org/ UrlRegex: ^http://www\.blog(cdn|smithmedia)\.com/chinese\.engadget\.com/ UrlRegex: ^http://www\.chinasmile\.net/csnews/ UrlRegex: ^http://www\.greenpeace\.org/ UrlRegex: ^http://www\.kwcg\.ca/forum UrlRegex: ^http://www\.linkedin\.com/countserv/count/ UrlRegex: ^http://www\.manictime\.com/setup/ UrlRegex: ^http://www\.marxists\.org/chinese UrlRegex: ^https://(.[^/])*\.*\baddthis\.com/ UrlRegex: ^https://(.[^/])*\.*\barchive\.is/ UrlRegex: ^https://(.[^/])*\.*\barchive\.org/ UrlRegex: ^https://(.[^/])*\.*\bbtdigg\.org/ UrlRegex: ^https://(.[^/])*\.*\bchange\.org/ UrlRegex: ^https://(.[^/])*\.*\bcloudflare\.com/ UrlRegex: ^https://(.[^/])*\.*\bcloudfront\.net/ UrlRegex: ^https://(.[^/])*\.*\bdropbox\.com/ UrlRegex: ^https://(.[^/])*\.*\bduckduckgo\.com/ UrlRegex: ^https://(.[^/])*\.*\bfeedly\.com/ UrlRegex: ^https://(.[^/])*\.*\bgetpocket\.com/ UrlRegex: ^https://(.[^/])*\.*\bhootsuite\.com/ UrlRegex: ^https://(.[^/])*\.*\binstagram.com/ UrlRegex: ^https://(.[^/])*\.*\bmassrelevance\.com/ UrlRegex: ^https://(.[^/])*\.*\bmediawiki\.org/ UrlRegex: ^https://(.[^/])*\.*\bonebitbug\.me/ UrlRegex: ^https://(.[^/])*\.*\bopendns\.com/ UrlRegex: ^https://(.[^/])*\.*\bpastebin\.com/ UrlRegex: ^https://(.[^/])*\.*\bpsiphon\.ca/ UrlRegex: ^https://(.[^/])*\.*\bpsiphon3\.com/ UrlRegex: ^https://(.[^/])*\.*\bptt\.cc/ UrlRegex: ^https://(.[^/])*\.*\bscribd\.com/ UrlRegex: ^https://(.[^/])*\.*\bsharethis\.com/ UrlRegex: ^https://(.[^/])*\.*\bsitemeter\.com/ UrlRegex: ^https://(.[^/])*\.*\bthestandnews\.com/ UrlRegex: ^https://(.[^/])*\.*\btorproject\.org/ UrlRegex: ^https://(.[^/])*\.*\btorrentz\.eu/ UrlRegex: ^https://(.[^/])*\.*\bvimeo\.com/ UrlRegex: ^https://(.[^/])*\.*\bwiki(source|quote|books|news|voyage|versity|data|media|pedia)\.org/ UrlRegex: ^https://(.[^/])*\.*\bwikimediafoundation\.org/ UrlRegex: ^https://(.[^/])*\.*\bwiktionary\.org/ UrlRegex: ^https://(.[^/])*\.*\bwordpress\.com/ UrlRegex: ^https://(.[^/])*\.*\bxing\.com/ UrlRegex: ^https://(.[^/])*\.*\byahoo\.(com|com\.(hk|tw)|co\.jp)/ UrlRegex: ^https://www\.gstatic\.cn/onebox/weather/ UrlRegex: ^https://www\.wireshark\.org/ vlog.xuite.net wenda.google.com.hk wikipedia.org woeser.middle-way.net www.asus.com www.blockedinchina.net www.hbogo.com www.melauto.it www.resilio.com www.savetube.com www.teamviewer.com yuming.flnet.org *.kkbox.com *.c555.me *.abc.net.au www.moneymanagerex.org *.c700.me *.tunemymusic.com *.pixnet.net *.showmore.com *.socpk.com *.imgur.com *.applealmond.com *.c996.me *.x996.me *.navismithapis-cdn.com *.hxmmdd.com *.uc555.me *.x000.me *.careerengine.us *.z-lib.org *.zlibcdn.com *.zlibcdn2.com *.jp1lib.org *.luckydesigner.space *.ifixit.com *.ujjainyoga.com *.uc555.me *.x666x.me *.duolingo.com *.im.ge *.weiming.info *.ctext.org *.zhangsn.me *.gamemale.com *.gstatic.com *.doubleclick.net *.ahhhhfs.com *.gstatic.com *.x888x.me *.bing.com *.mirror.xyz *.ikoolcore.com *.x222x.me *.fengzg.net *.commonhealth.com.tw *.pdf24.org *.dmm-extension.com *.p-smith.com *.dmm.co.jp *.dmm.com *.360yield.com sslwidget.criteo.com *.x555x.me *.qnap.com *.m1080.com *.hostloc.com *.x333x.me *.daum.net *.githubassets.com *.free.com.tw *.yimg.com *.wikihow.com *.x567x.me *.dropboxstatic.com *.uukanshu.com *.crifan.com *.ipsw.me *.pacom.mil *.ai1080.art *.ab8080.vip *.cc12345.vip *.apkpure.com *apkmonk.com *.uscardforum.com *.kmuh.org.tw *.aaag.me *.extrabux.com *.docker.com *.webpkgcache.com cdn.cookielaw.org image.winudf.com familyclic.hk *.hklii.hk *.vercel.app www.onenote.com *.ccgga.me *.ibbb.me *.caaba.me *.r3sub.com *.oaistatic.com *.4sqi.net *.ghgo.xyz *.fanmingming.com *.csp.withgoogle.com *.redd.it *.redditspace.com *.redditmedia.com *.xn--sss604efuw.top *.javrate.com *.fontawesome.com *.eroimg.net *.eroterest.net *.missav.com *.googletagmanager.com *.kakao.com *.kakaocorp.com *.bluesky-soft.com *.b-cdn.net copilot.microsoft.com *.z-lib.io *.z-lib.id *.jnn-pa.googleapis.com *.firebaseinstallations.googleapis.com *.firebase.googleapis.com *.hxmmdd.com *.heqrmudv.site *.translate.goog *.bahamut.com.tw *.speedtest.net assets.msn.cn *.hjwzw.com *.bg3.co *.x.com *.xhd.tw *.softonic.cn *.exifedit.com *.imgfor80.me *.chatgpt.com *.openai.com ================================================ FILE: dns.routes ================================================ 223.5.5.5/32 223.6.6.6/32 2400:3200::1/128 2400:3200:baba::1/128 119.29.29.29/32 119.28.28.28/32 1.12.12.12/32 120.53.53.53/32 2402:4e00::/128 2402:4e00:1::/128 180.76.76.76/32 2400:da00::6666/128 114.114.114.114/32 114.114.115.115/32 180.184.1.1/32 180.184.2.2/32 101.226.4.6/32 218.30.118.6/32 123.125.81.6/32 140.207.198.6/32 domain:dns.alidns.com domain:doh.pub domain:dot.pub domain:doh.360.cn domain:dot.360.cn ================================================ FILE: dropbox_fs_fix.patch ================================================ --- /pre_fix/dropbox.py 2019-06-19 03:02:17.000000000 +0800 +++ /after/dropbox.py 2019-07-10 01:31:14.000000000 +0800 @@ -748,6 +748,11 @@ return newmeth def start_dropbox(): + lib_path = os.path.join(DROPBOX_DIST_PATH, "libdropbox_fs_fix.so") + if os.path.exists(lib_path): + console_print(u"\ndropbox: load filesystem fix library") + os.environ["LD_PRELOAD"] = lib_path + if os.access(DROPBOXD_PATH, os.X_OK): f = open("/dev/null", "w") # we don't reap the child because we're gonna die anyway, let init do it ================================================ FILE: fcm.hosts ================================================ 127.0.0.1 localhost ::1 localhost 108.177.125.188 mtalk.google.com 64.233.188.188 alt1-mtalk.google.com 74.125.24.188 alt2-mtalk.google.com 74.125.200.188 alt3-mtalk.google.com 173.194.174.188 alt4-mtalk.google.com 172.217.194.188 alt5-mtalk.google.com 142.250.4.188 alt6-mtalk.google.com 142.251.175.188 alt7-mtalk.google.com 108.177.97.188 alt8-mtalk.google.com 2404:6800:4008:c1b::bc mtalk.google.com 2404:6800:4008:c01::bc alt1-mtalk.google.com 2404:6800:4008:c02::bc alt2-mtalk.google.com 2404:6800:4008:c03::bc alt3-mtalk.google.com 2404:6800:4003:c00::bc alt4-mtalk.google.com 2404:6800:4008:c07::bc alt5-mtalk.google.com 2404:6800:4003:c11::bc alt6-mtalk.google.com 2404:6800:4008:c13::bc alt7-mtalk.google.com 2404:6800:4008:c19::bc alt8-mtalk.google.com ================================================ FILE: ffmpeg-let-rtmp-flv-support-hevc-h265-opus.patch ================================================ RTMP Protocol & FLV Encode/Decode Support H.265/HEVC & OPUS diff --color -uNr a/libavformat/flvdec.c b/libavformat/flvdec.c --- a/libavformat/flvdec.c 2020-07-11 18:39:30.000000000 +0800 +++ b/libavformat/flvdec.c 2020-12-29 19:24:42.400006406 +0800 @@ -36,6 +36,7 @@ #include "internal.h" #include "avio_internal.h" #include "flv.h" +#include "hevc.h" #define VALIDATE_INDEX_TS_THRESH 2500 @@ -233,6 +234,11 @@ case FLV_CODECID_PCM_ALAW: return apar->sample_rate == 8000 && apar->codec_id == AV_CODEC_ID_PCM_ALAW; + case FLV_CODECID_OPUS: + return apar->sample_rate == 48000 && + apar->channels == 2 && + apar->bits_per_coded_sample == 16 && + apar->codec_id == AV_CODEC_ID_OPUS; default: return apar->codec_tag == (flv_codecid >> FLV_AUDIO_CODECID_OFFSET); } @@ -291,6 +297,12 @@ apar->sample_rate = 8000; apar->codec_id = AV_CODEC_ID_PCM_ALAW; break; + case FLV_CODECID_OPUS: + apar->sample_rate = 48000; + apar->channels = 2; + apar->bits_per_coded_sample = 16; + apar->codec_id = AV_CODEC_ID_OPUS; + break; default: avpriv_request_sample(s, "Audio codec (%x)", flv_codecid >> FLV_AUDIO_CODECID_OFFSET); @@ -318,6 +330,8 @@ return vpar->codec_id == AV_CODEC_ID_VP6A; case FLV_CODECID_H264: return vpar->codec_id == AV_CODEC_ID_H264; + case FLV_CODECID_HEVC: + return vpar->codec_id == AV_CODEC_ID_HEVC; default: return vpar->codec_tag == flv_codecid; } @@ -367,6 +381,11 @@ par->codec_id = AV_CODEC_ID_MPEG4; ret = 3; break; + case FLV_CODECID_HEVC: + par->codec_id = AV_CODEC_ID_HEVC; + vstream->need_parsing = AVSTREAM_PARSE_NONE; + ret = 3; + break; default: avpriv_request_sample(s, "Video codec (%x)", flv_codecid); par->codec_tag = flv_codecid; @@ -1222,7 +1241,8 @@ if (st->codecpar->codec_id == AV_CODEC_ID_AAC || st->codecpar->codec_id == AV_CODEC_ID_H264 || - st->codecpar->codec_id == AV_CODEC_ID_MPEG4) { + st->codecpar->codec_id == AV_CODEC_ID_MPEG4 || + st->codecpar->codec_id == AV_CODEC_ID_HEVC ) { int type = avio_r8(s->pb); size--; @@ -1231,7 +1251,7 @@ goto leave; } - if (st->codecpar->codec_id == AV_CODEC_ID_H264 || st->codecpar->codec_id == AV_CODEC_ID_MPEG4) { + if (st->codecpar->codec_id == AV_CODEC_ID_H264 || st->codecpar->codec_id == AV_CODEC_ID_MPEG4 || st->codecpar->codec_id == AV_CODEC_ID_HEVC) { // sign extension int32_t cts = (avio_rb24(s->pb) + 0xff800000) ^ 0xff800000; pts = dts + cts; @@ -1247,7 +1267,7 @@ } } if (type == 0 && (!st->codecpar->extradata || st->codecpar->codec_id == AV_CODEC_ID_AAC || - st->codecpar->codec_id == AV_CODEC_ID_H264)) { + st->codecpar->codec_id == AV_CODEC_ID_H264 || st->codecpar->codec_id == AV_CODEC_ID_HEVC)) { AVDictionaryEntry *t; if (st->codecpar->extradata) { diff --color -uNr a/libavformat/flvenc.c b/libavformat/flvenc.c --- a/libavformat/flvenc.c 2020-07-11 18:39:30.000000000 +0800 +++ b/libavformat/flvenc.c 2020-12-29 19:36:34.543776338 +0800 @@ -34,7 +34,7 @@ #include "libavutil/opt.h" #include "libavcodec/put_bits.h" #include "libavcodec/aacenctab.h" - +#include "hevc.h" static const AVCodecTag flv_video_codec_ids[] = { { AV_CODEC_ID_FLV1, FLV_CODECID_H263 }, @@ -46,6 +46,7 @@ { AV_CODEC_ID_VP6, FLV_CODECID_VP6 }, { AV_CODEC_ID_VP6A, FLV_CODECID_VP6A }, { AV_CODEC_ID_H264, FLV_CODECID_H264 }, + { AV_CODEC_ID_HEVC, FLV_CODECID_HEVC }, { AV_CODEC_ID_NONE, 0 } }; @@ -60,6 +61,7 @@ { AV_CODEC_ID_PCM_MULAW, FLV_CODECID_PCM_MULAW >> FLV_AUDIO_CODECID_OFFSET }, { AV_CODEC_ID_PCM_ALAW, FLV_CODECID_PCM_ALAW >> FLV_AUDIO_CODECID_OFFSET }, { AV_CODEC_ID_SPEEX, FLV_CODECID_SPEEX >> FLV_AUDIO_CODECID_OFFSET }, + { AV_CODEC_ID_OPUS, FLV_CODECID_OPUS >> FLV_AUDIO_CODECID_OFFSET }, { AV_CODEC_ID_NONE, 0 } }; @@ -132,6 +134,9 @@ if (par->codec_id == AV_CODEC_ID_AAC) // specs force these parameters return FLV_CODECID_AAC | FLV_SAMPLERATE_44100HZ | FLV_SAMPLESSIZE_16BIT | FLV_STEREO; + else if (par->codec_id == AV_CODEC_ID_OPUS) // specs force these parameters + return FLV_CODECID_OPUS | FLV_SAMPLERATE_44100HZ | + FLV_SAMPLESSIZE_16BIT | FLV_STEREO; else if (par->codec_id == AV_CODEC_ID_SPEEX) { if (par->sample_rate != 16000) { av_log(s, AV_LOG_ERROR, @@ -491,7 +496,7 @@ FLVContext *flv = s->priv_data; if (par->codec_id == AV_CODEC_ID_AAC || par->codec_id == AV_CODEC_ID_H264 - || par->codec_id == AV_CODEC_ID_MPEG4) { + || par->codec_id == AV_CODEC_ID_MPEG4 || par->codec_id == AV_CODEC_ID_HEVC) { int64_t pos; avio_w8(pb, par->codec_type == AVMEDIA_TYPE_VIDEO ? @@ -537,7 +542,11 @@ avio_w8(pb, par->codec_tag | FLV_FRAME_KEY); // flags avio_w8(pb, 0); // AVC sequence header avio_wb24(pb, 0); // composition time - ff_isom_write_avcc(pb, par->extradata, par->extradata_size); + if (par->codec_id == AV_CODEC_ID_HEVC) { + ff_isom_write_hvcc(pb, par->extradata, par->extradata_size, 0); + } else { + ff_isom_write_avcc(pb, par->extradata, par->extradata_size); + } } data_size = avio_tell(pb) - pos; avio_seek(pb, -data_size - 10, SEEK_CUR); @@ -844,7 +853,7 @@ AVCodecParameters *par = s->streams[i]->codecpar; FLVStreamContext *sc = s->streams[i]->priv_data; if (par->codec_type == AVMEDIA_TYPE_VIDEO && - (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4)) + (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4 || par->codec_id == AV_CODEC_ID_HEVC)) put_avc_eos_tag(pb, sc->last_ts); } } @@ -895,13 +904,13 @@ if (par->codec_id == AV_CODEC_ID_VP6F || par->codec_id == AV_CODEC_ID_VP6A || par->codec_id == AV_CODEC_ID_VP6 || par->codec_id == AV_CODEC_ID_AAC) flags_size = 2; - else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) + else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4 || par->codec_id == AV_CODEC_ID_HEVC) flags_size = 5; else flags_size = 1; if (par->codec_id == AV_CODEC_ID_AAC || par->codec_id == AV_CODEC_ID_H264 - || par->codec_id == AV_CODEC_ID_MPEG4) { + || par->codec_id == AV_CODEC_ID_MPEG4 || par->codec_id == AV_CODEC_ID_HEVC) { int side_size = 0; uint8_t *side = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size); if (side && side_size > 0 && (side_size != par->extradata_size || memcmp(side, par->extradata, side_size))) { @@ -966,6 +975,10 @@ if (par->extradata_size > 0 && *(uint8_t*)par->extradata != 1) if ((ret = ff_avc_parse_nal_units_buf(pkt->data, &data, &size)) < 0) return ret; + } else if (par->codec_id == AV_CODEC_ID_HEVC) { + if (par->extradata_size > 0 && *(uint8_t*)par->extradata != 1) + if ((ret = ff_hevc_annexb2mp4_buf(pkt->data, &data, &size, 0, NULL)) < 0) + return ret; } else if (par->codec_id == AV_CODEC_ID_AAC && pkt->size > 2 && (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) { if (!s->streams[pkt->stream_index]->nb_frames) { @@ -1036,9 +1049,9 @@ else avio_w8(pb, ((FFALIGN(par->width, 16) - par->width) << 4) | (FFALIGN(par->height, 16) - par->height)); - } else if (par->codec_id == AV_CODEC_ID_AAC) + } else if (par->codec_id == AV_CODEC_ID_AAC) { avio_w8(pb, 1); // AAC raw - else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) { + } else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4 || par->codec_id == AV_CODEC_ID_HEVC) { avio_w8(pb, 1); // AVC NALU avio_wb24(pb, pkt->pts - pkt->dts); } diff --color -uNr a/libavformat/flv.h b/libavformat/flv.h --- a/libavformat/flv.h 2020-07-09 17:17:46.000000000 +0800 +++ b/libavformat/flv.h 2020-12-29 19:10:08.444114140 +0800 @@ -99,6 +99,7 @@ FLV_CODECID_PCM_MULAW = 8 << FLV_AUDIO_CODECID_OFFSET, FLV_CODECID_AAC = 10<< FLV_AUDIO_CODECID_OFFSET, FLV_CODECID_SPEEX = 11<< FLV_AUDIO_CODECID_OFFSET, + FLV_CODECID_OPUS = 13<< FLV_AUDIO_CODECID_OFFSET, }; enum { @@ -110,6 +111,7 @@ FLV_CODECID_H264 = 7, FLV_CODECID_REALH263= 8, FLV_CODECID_MPEG4 = 9, + FLV_CODECID_HEVC = 12, }; enum { ================================================ FILE: gl_mt1300_led_daemon.patch ================================================ --- a 2022-05-20 07:38:48.486091422 +0800 +++ b 2022-05-20 07:38:58.000336930 +0800 @@ -1,15 +1,14 @@ #!/bin/sh +cycle=30 +tally=0 status="0" -count=`uci get mwan3.wan.count 2>/dev/null` -timeout=`uci get mwan3.wan.timeout 2>/dev/null` -track_ip=`uci get mwan3.wan.track_ip 2>/dev/null` -[ -z "$count" ] && count="1" -[ -z "$timeout" ] && timeout="2" -[ -z "$track_ip" ] && track_ip="8.8.4.4 8.8.8.8 208.67.222.222 208.67.220.220" +count="1" +timeout="2" +track_ip="223.5.5.5 180.76.76.76 119.29.29.29 114.114.114.114 1.1.1.1 8.8.8.8 4.2.2.1 208.67.222.222" mt1300_led off -mt1300_led blue_breath daemon +mt1300_led blue_flash for ip in $track_ip; do @@ -25,8 +24,19 @@ do if [ "$status" = "0" ];then mt1300_led blue_breath daemon + tally=0 else - mt1300_led white daemon + if [ -d /sys/class/net/tun* ]; then + tally=0 + mt1300_led both daemon + else + let tally+=1 + if [ $tally -lt $cycle ];then + mt1300_led white daemon + else + mt1300_led off + fi + fi fi sleep 5 ================================================ FILE: keys.dict ================================================ 00085e2ca32c 010203040506 06a95de06dd5 0aa5f10bd6d5 0ca3eec5db81 123456789abc 123456abcdef 1a2b3c4d5e6f 1a982c7e459a 228e01723e31 2299b5aaf7da 22bfbb0908d5 4364d798e9b4 454445ad9115 474249434351 475fcdff567f 4b1c77a4d2c1 4d3a99c351dd 4e762c24ad1c 533cb6c723f6 587ee5f9350f 5f5bfd7d4e5b 5f7beff15e5b 634d53dd81e3 64d14ed565f9 68de227c8b11 6b3c56d6c903 6cd44c605402 6eaa4eac3d2a 714c5c886e97 76ea4eaf6b62 782568615503 7861609b0d88 836dd5d2498a 882551c9d880 899261486a28 8fd0a4f256e9 a0478cc39091 a0a1a2a3a4a5 a5a412d418ec aabbccddeeff abcdef123456 af88173051f5 b03646f7ef1c b0b1b2b3b4b5 b9884110200d be0e3297d1c4 c0c1c2c3c4c5 d0141ce2327e d0d1d2d3d4d5 d3f7d3f7d3f7 db8876f2cb27 dde63639976e e1019b602902 e1ea7b8ee45e e9e31129119e effeece2a3de f7f73a8f7d82 fb7f2a19522b fe7bff776eff feffcfff7f5b ffdfcf7ff6df ================================================ FILE: nginx.patch ================================================ Add HTTP2 HPACK Encoding Support. Add Dynamic TLS Record Support. Using: patch -p1 < nginx.patch diff --color -uNr a/auto/modules b/auto/modules --- a/auto/modules 2023-05-23 23:08:20.000000000 +0800 +++ b/auto/modules 2023-05-24 00:52:16.901966472 +0800 @@ -466,6 +466,10 @@ . auto/module fi + if [ $HTTP_V2_HPACK_ENC = YES ]; then + have=NGX_HTTP_V2_HPACK_ENC . auto/have + fi + if :; then ngx_module_name=ngx_http_static_module ngx_module_incs= diff --color -uNr a/auto/options b/auto/options --- a/auto/options 2023-05-23 23:08:20.000000000 +0800 +++ b/auto/options 2023-05-24 00:54:34.532597378 +0800 @@ -61,6 +61,7 @@ HTTP_GZIP=YES HTTP_SSL=NO HTTP_V2=NO +HTTP_V2_HPACK_ENC=NO HTTP_V3=NO HTTP_SSI=YES HTTP_REALIP=NO @@ -236,6 +237,7 @@ --with-http_ssl_module) HTTP_SSL=YES ;; --with-http_v2_module) HTTP_V2=YES ;; + --with-http_v2_hpack_enc) HTTP_V2_HPACK_ENC=YES ;; --with-http_v3_module) HTTP_V3=YES ;; --with-http_realip_module) HTTP_REALIP=YES ;; --with-http_addition_module) HTTP_ADDITION=YES ;; @@ -456,6 +458,7 @@ --with-http_ssl_module enable ngx_http_ssl_module --with-http_v2_module enable ngx_http_v2_module + --with-http_v2_hpack_enc enable ngx_http_v2_hpack_enc --with-http_v3_module enable ngx_http_v3_module --with-http_realip_module enable ngx_http_realip_module --with-http_addition_module enable ngx_http_addition_module diff --color -uNr a/src/core/ngx_murmurhash.c b/src/core/ngx_murmurhash.c --- a/src/core/ngx_murmurhash.c 2023-05-23 23:08:20.000000000 +0800 +++ b/src/core/ngx_murmurhash.c 2023-05-24 00:52:16.902966499 +0800 @@ -50,3 +50,63 @@ return h; } + + +uint64_t +ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed) +{ + uint64_t h, k; + + h = seed ^ len; + + while (len >= 8) { + k = data[0]; + k |= data[1] << 8; + k |= data[2] << 16; + k |= data[3] << 24; + k |= (uint64_t)data[4] << 32; + k |= (uint64_t)data[5] << 40; + k |= (uint64_t)data[6] << 48; + k |= (uint64_t)data[7] << 56; + + k *= 0xc6a4a7935bd1e995ull; + k ^= k >> 47; + k *= 0xc6a4a7935bd1e995ull; + + h ^= k; + h *= 0xc6a4a7935bd1e995ull; + + data += 8; + len -= 8; + } + + switch (len) { + case 7: + h ^= (uint64_t)data[6] << 48; + /* fall through */ + case 6: + h ^= (uint64_t)data[5] << 40; + /* fall through */ + case 5: + h ^= (uint64_t)data[4] << 32; + /* fall through */ + case 4: + h ^= data[3] << 24; + /* fall through */ + case 3: + h ^= data[2] << 16; + /* fall through */ + case 2: + h ^= data[1] << 8; + /* fall through */ + case 1: + h ^= data[0]; + h *= 0xc6a4a7935bd1e995ull; + } + + h ^= h >> 47; + h *= 0xc6a4a7935bd1e995ull; + h ^= h >> 47; + + return h; +} diff --color -uNr a/src/core/ngx_murmurhash.h b/src/core/ngx_murmurhash.h --- a/src/core/ngx_murmurhash.h 2023-05-23 23:08:20.000000000 +0800 +++ b/src/core/ngx_murmurhash.h 2023-05-24 00:52:16.903966525 +0800 @@ -15,5 +15,7 @@ uint32_t ngx_murmur_hash2(u_char *data, size_t len); +uint64_t ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed); + #endif /* _NGX_MURMURHASH_H_INCLUDED_ */ diff --color -uNr a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c 2023-05-23 23:08:20.000000000 +0800 +++ b/src/event/ngx_event_openssl.c 2023-05-24 00:52:16.905966578 +0800 @@ -1674,6 +1674,7 @@ sc->buffer = ((flags & NGX_SSL_BUFFER) != 0); sc->buffer_size = ssl->buffer_size; + sc->dyn_rec = ssl->dyn_rec; sc->session_ctx = ssl->ctx; @@ -2645,6 +2646,41 @@ for ( ;; ) { + /* Dynamic record resizing: + We want the initial records to fit into one TCP segment + so we don't get TCP HoL blocking due to TCP Slow Start. + A connection always starts with small records, but after + a given amount of records sent, we make the records larger + to reduce header overhead. + After a connection has idled for a given timeout, begin + the process from the start. The actual parameters are + configurable. If dyn_rec_timeout is 0, we assume dyn_rec is off. */ + + if (c->ssl->dyn_rec.timeout > 0 ) { + + if (ngx_current_msec - c->ssl->dyn_rec_last_write > + c->ssl->dyn_rec.timeout) + { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + c->ssl->dyn_rec_records_sent = 0; + + } else { + if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold * 2) + { + buf->end = buf->start + c->ssl->buffer_size; + + } else if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold) + { + buf->end = buf->start + c->ssl->dyn_rec.size_hi; + + } else { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + } + } + } + while (in && buf->last < buf->end && send < limit) { if (in->buf->last_buf || in->buf->flush) { flush = 1; @@ -2784,6 +2820,9 @@ if (n > 0) { + c->ssl->dyn_rec_records_sent++; + c->ssl->dyn_rec_last_write = ngx_current_msec; + if (c->ssl->saved_read_handler) { c->read->handler = c->ssl->saved_read_handler; diff --color -uNr a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h 2023-05-23 23:08:20.000000000 +0800 +++ b/src/event/ngx_event_openssl.h 2023-05-24 00:52:16.906966604 +0800 @@ -86,10 +86,19 @@ typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; +typedef struct { + ngx_msec_t timeout; + ngx_uint_t threshold; + size_t size_lo; + size_t size_hi; +} ngx_ssl_dyn_rec_t; + + struct ngx_ssl_s { SSL_CTX *ctx; ngx_log_t *log; size_t buffer_size; + ngx_ssl_dyn_rec_t dyn_rec; }; @@ -128,6 +137,10 @@ unsigned in_ocsp:1; unsigned early_preread:1; unsigned write_blocked:1; + + ngx_ssl_dyn_rec_t dyn_rec; + ngx_msec_t dyn_rec_last_write; + ngx_uint_t dyn_rec_records_sent; }; @@ -137,7 +150,7 @@ #define NGX_SSL_DFLT_BUILTIN_SCACHE -5 -#define NGX_SSL_MAX_SESSION_SIZE 4096 +#define NGX_SSL_MAX_SESSION_SIZE 16384 typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t; diff --color -uNr a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c 2023-05-23 23:08:20.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.c 2023-05-24 00:52:16.906966604 +0800 @@ -304,6 +304,41 @@ offsetof(ngx_http_ssl_srv_conf_t, reject_handshake), NULL }, + { ngx_string("ssl_dyn_rec_enable"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_enable), + NULL }, + + { ngx_string("ssl_dyn_rec_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_timeout), + NULL }, + + { ngx_string("ssl_dyn_rec_size_lo"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_lo), + NULL }, + + { ngx_string("ssl_dyn_rec_size_hi"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_hi), + NULL }, + + { ngx_string("ssl_dyn_rec_threshold"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_threshold), + NULL }, + ngx_null_command }; @@ -636,6 +671,11 @@ sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; sscf->stapling = NGX_CONF_UNSET; sscf->stapling_verify = NGX_CONF_UNSET; + sscf->dyn_rec_enable = NGX_CONF_UNSET; + sscf->dyn_rec_timeout = NGX_CONF_UNSET_MSEC; + sscf->dyn_rec_size_lo = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_size_hi = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_threshold = NGX_CONF_UNSET_UINT; return sscf; } @@ -712,6 +752,20 @@ ngx_conf_merge_str_value(conf->stapling_responder, prev->stapling_responder, ""); + ngx_conf_merge_value(conf->dyn_rec_enable, prev->dyn_rec_enable, 0); + ngx_conf_merge_msec_value(conf->dyn_rec_timeout, prev->dyn_rec_timeout, + 1000); + /* Default sizes for the dynamic record sizes are defined to fit maximal + TLS + IPv6 overhead in a single TCP segment for lo and 3 segments for hi: + 1369 = 1500 - 40 (IP) - 20 (TCP) - 10 (Time) - 61 (Max TLS overhead) */ + ngx_conf_merge_size_value(conf->dyn_rec_size_lo, prev->dyn_rec_size_lo, + 1369); + /* 4229 = (1500 - 40 - 20 - 10) * 3 - 61 */ + ngx_conf_merge_size_value(conf->dyn_rec_size_hi, prev->dyn_rec_size_hi, + 4229); + ngx_conf_merge_uint_value(conf->dyn_rec_threshold, prev->dyn_rec_threshold, + 40); + conf->ssl.log = cf->log; if (conf->enable) { @@ -938,6 +992,28 @@ return NGX_CONF_ERROR; } + if (conf->dyn_rec_enable) { + conf->ssl.dyn_rec.timeout = conf->dyn_rec_timeout; + conf->ssl.dyn_rec.threshold = conf->dyn_rec_threshold; + + if (conf->buffer_size > conf->dyn_rec_size_lo) { + conf->ssl.dyn_rec.size_lo = conf->dyn_rec_size_lo; + + } else { + conf->ssl.dyn_rec.size_lo = conf->buffer_size; + } + + if (conf->buffer_size > conf->dyn_rec_size_hi) { + conf->ssl.dyn_rec.size_hi = conf->dyn_rec_size_hi; + + } else { + conf->ssl.dyn_rec.size_hi = conf->buffer_size; + } + + } else { + conf->ssl.dyn_rec.timeout = 0; + } + return NGX_CONF_OK; } diff --color -uNr a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h 2023-05-23 23:08:20.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.h 2023-05-24 00:52:16.907966630 +0800 @@ -67,6 +67,12 @@ u_char *file; ngx_uint_t line; + + ngx_flag_t dyn_rec_enable; + ngx_msec_t dyn_rec_timeout; + size_t dyn_rec_size_lo; + size_t dyn_rec_size_hi; + ngx_uint_t dyn_rec_threshold; } ngx_http_ssl_srv_conf_t; diff --color -uNr a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c --- a/src/http/v2/ngx_http_v2.c 2023-05-23 23:08:20.000000000 +0800 +++ b/src/http/v2/ngx_http_v2.c 2023-05-24 00:52:16.909966683 +0800 @@ -274,6 +274,8 @@ h2c->frame_size = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + h2c->max_hpack_table_size = NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE; + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); h2c->concurrent_pushes = h2scf->concurrent_pushes; @@ -2280,6 +2282,14 @@ case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING: h2c->table_update = 1; + + if (value > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { + h2c->max_hpack_table_size = NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE; + } else { + h2c->max_hpack_table_size = value; + } + + h2c->indicate_resize = 1; break; default: diff --color -uNr a/src/http/v2/ngx_http_v2_encode.c b/src/http/v2/ngx_http_v2_encode.c --- a/src/http/v2/ngx_http_v2_encode.c 2023-05-23 23:08:20.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_encode.c 2023-05-24 00:52:16.909966683 +0800 @@ -10,7 +10,7 @@ #include -static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, +u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); @@ -40,7 +40,7 @@ } -static u_char * +u_char * ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) { if (value < prefix) { diff --color -uNr a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c --- a/src/http/v2/ngx_http_v2_filter_module.c 2023-05-23 23:08:20.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_filter_module.c 2023-05-24 00:52:16.910966710 +0800 @@ -23,10 +23,53 @@ #define ngx_http_v2_literal_size(h) \ (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1) +#define ngx_http_v2_indexed(i) (128 + (i)) +#define ngx_http_v2_inc_indexed(i) (64 + (i)) + +#define NGX_HTTP_V2_ENCODE_RAW 0 +#define NGX_HTTP_V2_ENCODE_HUFF 0x80 + +#define NGX_HTTP_V2_AUTHORITY_INDEX 1 +#define NGX_HTTP_V2_METHOD_GET_INDEX 2 +#define NGX_HTTP_V2_PATH_INDEX 4 + +#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6 +#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7 + +#define NGX_HTTP_V2_STATUS_INDEX 8 +#define NGX_HTTP_V2_STATUS_200_INDEX 8 +#define NGX_HTTP_V2_STATUS_204_INDEX 9 +#define NGX_HTTP_V2_STATUS_206_INDEX 10 +#define NGX_HTTP_V2_STATUS_304_INDEX 11 +#define NGX_HTTP_V2_STATUS_400_INDEX 12 +#define NGX_HTTP_V2_STATUS_404_INDEX 13 +#define NGX_HTTP_V2_STATUS_500_INDEX 14 + +#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 +#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 +#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 +#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 +#define NGX_HTTP_V2_DATE_INDEX 33 +#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 +#define NGX_HTTP_V2_LOCATION_INDEX 46 +#define NGX_HTTP_V2_SERVER_INDEX 54 +#define NGX_HTTP_V2_USER_AGENT_INDEX 58 +#define NGX_HTTP_V2_VARY_INDEX 59 #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 +static const struct { + u_char *name; + u_char const len; +} push_header[] = { + { (u_char*)":authority" , 10 }, + { (u_char*)"accept-encoding" , 15 }, + { (u_char*)"accept-language" , 15 }, + { (u_char*)"user-agent" , 10 } +}; + + typedef struct { ngx_str_t name; u_char index; @@ -155,11 +198,9 @@ #endif static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER); - static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)]; static size_t nginx_ver_build_len = ngx_http_v2_literal_size(NGINX_VER_BUILD); - static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)]; stream = r->stream; @@ -435,7 +476,7 @@ } tmp = ngx_palloc(r->pool, tmp_len); - pos = ngx_pnalloc(r->pool, len); + pos = ngx_pnalloc(r->pool, len + 15 + 1); if (pos == NULL || tmp == NULL) { return NGX_ERROR; @@ -443,11 +484,16 @@ start = pos; - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; + h2c = r->stream->connection; + + if (h2c->indicate_resize) { + *pos = 32; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), + h2c->max_hpack_table_size); + h2c->indicate_resize = 0; +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_table_resize(h2c); +#endif } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, @@ -458,67 +504,28 @@ *pos++ = status; } else { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); - *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3; - pos = ngx_sprintf(pos, "%03ui", r->headers_out.status); + ngx_sprintf(pos + 8, "%O3ui", r->headers_out.status); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)":status", + sizeof(":status") - 1, pos + 8, 3, tmp); } if (r->headers_out.server == NULL) { - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: %s\"", - NGINX_VER); - - } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: %s\"", - NGINX_VER_BUILD); - - } else { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: nginx\""); - } - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); - - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - if (nginx_ver[0] == '\0') { - p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER, - sizeof(NGINX_VER) - 1, tmp); - nginx_ver_len = p - nginx_ver; - } - - pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len); + pos = ngx_http_v2_write_header_str("server", NGINX_VER); } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - if (nginx_ver_build[0] == '\0') { - p = ngx_http_v2_write_value(nginx_ver_build, - (u_char *) NGINX_VER_BUILD, - sizeof(NGINX_VER_BUILD) - 1, tmp); - nginx_ver_build_len = p - nginx_ver_build; - } - - pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len); + pos = ngx_http_v2_write_header_str("server", NGINX_VER_BUILD); } else { - pos = ngx_cpymem(pos, nginx, sizeof(nginx)); + pos = ngx_http_v2_write_header_str("server", "nginx"); } } if (r->headers_out.date == NULL) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"date: %V\"", - &ngx_cached_http_time); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); - pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data, - ngx_cached_http_time.len, tmp); + pos = ngx_http_v2_write_header_tbl("date", ngx_cached_http_time); } if (r->headers_out.content_type.len) { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); - if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { @@ -544,64 +551,36 @@ r->headers_out.content_type.data = p - len; } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"content-type: %V\"", - &r->headers_out.content_type); - - pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data, - r->headers_out.content_type.len, tmp); + pos = ngx_http_v2_write_header_tbl("content-type", + r->headers_out.content_type); } if (r->headers_out.content_length == NULL && r->headers_out.content_length_n >= 0) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"content-length: %O\"", - r->headers_out.content_length_n); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX); - - p = pos; - pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n); - *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1); + p = ngx_sprintf(pos + 15, "%O", r->headers_out.content_length_n); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"content-length", + sizeof("content-length") - 1, pos + 15, + p - (pos + 15), tmp); } if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); - - ngx_http_time(pos, r->headers_out.last_modified_time); + ngx_http_time(pos + 14, r->headers_out.last_modified_time); len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"last-modified: %*s\"", - len, pos); - - /* - * Date will always be encoded using huffman in the temporary buffer, - * so it's safe here to use src and dst pointing to the same address. - */ - pos = ngx_http_v2_write_value(pos, pos, len, tmp); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"last-modified", + sizeof("last-modified") - 1, pos + 14, + len, tmp); } if (r->headers_out.location && r->headers_out.location->value.len) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"location: %V\"", - &r->headers_out.location->value); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); - pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data, - r->headers_out.location->value.len, tmp); + pos = ngx_http_v2_write_header_tbl("location", r->headers_out.location->value); } #if (NGX_HTTP_GZIP) if (r->gzip_vary) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"vary: Accept-Encoding\""); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); - pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding)); + pos = ngx_http_v2_write_header_str("vary", "Accept-Encoding"); } #endif @@ -624,23 +603,10 @@ continue; } -#if (NGX_DEBUG) - if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { - ngx_strlow(tmp, header[i].key.data, header[i].key.len); - - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"%*s: %V\"", - header[i].key.len, tmp, &header[i].value); - } -#endif - - *pos++ = 0; - - pos = ngx_http_v2_write_name(pos, header[i].key.data, - header[i].key.len, tmp); + pos = ngx_http_v2_write_header(h2c, pos, header[i].key.data, + header[i].key.len, header[i].value.data, + header[i].value.len, tmp); - pos = ngx_http_v2_write_value(pos, header[i].value.data, - header[i].value.len, tmp); } fin = r->header_only @@ -997,6 +963,7 @@ for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { len += binary[i].len; + len += push_header[i].len + 1; } pos = ngx_pnalloc(r->pool, len); @@ -1006,12 +973,17 @@ start = pos; - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; - } + h2c = r->stream->connection; + + if (h2c->indicate_resize) { + *pos = 32; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), + h2c->max_hpack_table_size); + h2c->indicate_resize = 0; +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_table_resize(h2c); +#endif + } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":method: GET\""); @@ -1021,8 +993,7 @@ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":path: %V\"", path); - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); - pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp); + pos = ngx_http_v2_write_header_pot(":path", path); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":scheme: %V\"", &r->schema); @@ -1047,11 +1018,15 @@ continue; } + value = &(*h)->value; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \"%V: %V\"", &ph[i].name, &(*h)->value); - pos = ngx_cpymem(pos, binary[i].data, binary[i].len); + pos = ngx_http_v2_write_header(h2c, pos, + push_header[i].name, push_header[i].len, value->data, value->len, + tmp); } frame = ngx_http_v2_create_push_frame(r, start, pos); diff --color -uNr a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h --- a/src/http/v2/ngx_http_v2.h 2023-05-23 23:08:20.000000000 +0800 +++ b/src/http/v2/ngx_http_v2.h 2023-05-24 00:52:16.911966736 +0800 @@ -51,6 +51,14 @@ #define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1) #define NGX_HTTP_V2_DEFAULT_WINDOW 65535 +#define HPACK_ENC_HTABLE_SZ 128 /* better to keep a PoT < 64k */ +#define HPACK_ENC_HTABLE_ENTRIES ((HPACK_ENC_HTABLE_SZ * 100) / 128) +#define HPACK_ENC_DYNAMIC_KEY_TBL_SZ 10 /* 10 is sufficient for most */ +#define HPACK_ENC_MAX_ENTRY 512 /* longest header size to match */ + +#define NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE 4096 +#define NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE 16384 /* < 64k */ + #define NGX_HTTP_V2_DEFAULT_WEIGHT 16 @@ -114,6 +122,46 @@ } ngx_http_v2_hpack_t; +#if (NGX_HTTP_V2_HPACK_ENC) +typedef struct { + uint64_t hash_val; + uint32_t index; + uint16_t pos; + uint16_t klen, vlen; + uint16_t size; + uint16_t next; +} ngx_http_v2_hpack_enc_entry_t; + + +typedef struct { + uint64_t hash_val; + uint32_t index; + uint16_t pos; + uint16_t klen; +} ngx_http_v2_hpack_name_entry_t; + + +typedef struct { + size_t size; /* size as defined in RFC 7541 */ + uint32_t top; /* the last entry */ + uint32_t pos; + uint16_t n_elems; /* number of elements */ + uint16_t base; /* index of the oldest entry */ + uint16_t last; /* index of the newest entry */ + + /* hash table for dynamic entries, instead using a generic hash table, + which would be too slow to process a significant amount of headers, + this table is not determenistic, and might ocasionally fail to insert + a value, at the cost of slightly worse compression, but significantly + faster performance */ + ngx_http_v2_hpack_enc_entry_t htable[HPACK_ENC_HTABLE_SZ]; + ngx_http_v2_hpack_name_entry_t heads[HPACK_ENC_DYNAMIC_KEY_TBL_SZ]; + u_char storage[NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE + + HPACK_ENC_MAX_ENTRY]; +} ngx_http_v2_hpack_enc_t; +#endif + + struct ngx_http_v2_connection_s { ngx_connection_t *connection; ngx_http_connection_t *http_connection; @@ -135,6 +183,8 @@ size_t frame_size; + size_t max_hpack_table_size; + ngx_queue_t waiting; ngx_http_v2_state_t state; @@ -164,6 +214,11 @@ unsigned blocked:1; unsigned goaway:1; unsigned push_disabled:1; + unsigned indicate_resize:1; + +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_hpack_enc_t hpack_enc; +#endif }; @@ -207,6 +262,8 @@ ngx_array_t *cookies; + size_t header_limit; + ngx_pool_t *pool; unsigned waiting:1; @@ -413,4 +470,35 @@ u_char *tmp, ngx_uint_t lower); +u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, + u_char *tmp, ngx_uint_t lower); + +u_char * +ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); + +#define ngx_http_v2_write_name(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 1) +#define ngx_http_v2_write_value(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 0) + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, u_char *value, size_t value_len, + u_char *tmp); + +void +ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c); + +#define ngx_http_v2_write_header_str(key, value) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + (u_char *) value, sizeof(value) - 1, tmp); + +#define ngx_http_v2_write_header_tbl(key, val) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + val.data, val.len, tmp); + +#define ngx_http_v2_write_header_pot(key, val) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + val->data, val->len, tmp); + #endif /* _NGX_HTTP_V2_H_INCLUDED_ */ diff --color -uNr a/src/http/v2/ngx_http_v2_table.c b/src/http/v2/ngx_http_v2_table.c --- a/src/http/v2/ngx_http_v2_table.c 2023-05-23 23:08:20.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_table.c 2023-05-24 00:52:16.912966762 +0800 @@ -361,3 +361,434 @@ return NGX_OK; } + + +#if (NGX_HTTP_V2_HPACK_ENC) + +static ngx_int_t +hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len); + +static ngx_int_t +hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, + uint8_t *key, size_t key_len); + + +void +ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c) +{ + ngx_http_v2_hpack_enc_entry_t *table; + uint64_t idx; + + table = h2c->hpack_enc.htable; + + while (h2c->hpack_enc.size > h2c->max_hpack_table_size) { + idx = h2c->hpack_enc.base; + h2c->hpack_enc.base = table[idx].next; + h2c->hpack_enc.size -= table[idx].size; + table[idx].hash_val = 0; + h2c->hpack_enc.n_elems--; + } +} + + +/* checks if a header is in the hpack table - if so returns the table entry, + otherwise encodes and inserts into the table and returns 0, + if failed to insert into table, returns -1 */ +static ngx_int_t +ngx_http_v2_table_encode_strings(ngx_http_v2_connection_t *h2c, + size_t key_len, size_t val_len, uint8_t *key, uint8_t *val, + ngx_int_t *header_idx) +{ + uint64_t hash_val, key_hash, idx, lru; + int i; + size_t size = key_len + val_len + 32; + uint8_t *storage = h2c->hpack_enc.storage; + + ngx_http_v2_hpack_enc_entry_t *table; + ngx_http_v2_hpack_name_entry_t *name; + + *header_idx = NGX_ERROR; + /* step 1: compute the hash value of header */ + if (size > HPACK_ENC_MAX_ENTRY || size > h2c->max_hpack_table_size) { + return NGX_ERROR; + } + + key_hash = ngx_murmur_hash2_64(key, key_len, 0x01234); + hash_val = ngx_murmur_hash2_64(val, val_len, key_hash); + + if (hash_val == 0) { + return NGX_ERROR; + } + + /* step 2: check if full header in the table */ + idx = hash_val; + i = -1; + while (idx) { + /* at most 8 locations are checked, but most will be done in 1 or 2 */ + table = &h2c->hpack_enc.htable[idx % HPACK_ENC_HTABLE_SZ]; + if (table->hash_val == hash_val + && table->klen == key_len + && table->vlen == val_len + && ngx_memcmp(key, storage + table->pos, key_len) == 0 + && ngx_memcmp(val, storage + table->pos + key_len, val_len) == 0) + { + return (h2c->hpack_enc.top - table->index) + 61; + } + + if (table->hash_val == 0 && i == -1) { + i = idx % HPACK_ENC_HTABLE_SZ; + break; + } + + idx >>= 8; + } + + /* step 3: check if key is in one of the tables */ + *header_idx = hpack_get_static_index(h2c, key, key_len); + + if (i == -1) { + return NGX_ERROR; + } + + if (*header_idx == NGX_ERROR) { + *header_idx = hpack_get_dynamic_index(h2c, key_hash, key, key_len); + } + + /* step 4: store the new entry */ + table = h2c->hpack_enc.htable; + + if (h2c->hpack_enc.top == 0xffffffff) { + /* just to be on the safe side, avoid overflow */ + ngx_memset(&h2c->hpack_enc, 0, sizeof(ngx_http_v2_hpack_enc_t)); + } + + while ((h2c->hpack_enc.size + size > h2c->max_hpack_table_size) + || h2c->hpack_enc.n_elems == HPACK_ENC_HTABLE_ENTRIES) { + /* make space for the new entry first */ + idx = h2c->hpack_enc.base; + h2c->hpack_enc.base = table[idx].next; + h2c->hpack_enc.size -= table[idx].size; + table[idx].hash_val = 0; + h2c->hpack_enc.n_elems--; + } + + table[i] = (ngx_http_v2_hpack_enc_entry_t){.hash_val = hash_val, + .index = h2c->hpack_enc.top, + .pos = h2c->hpack_enc.pos, + .klen = key_len, + .vlen = val_len, + .size = size, + .next = 0}; + + table[h2c->hpack_enc.last].next = i; + if (h2c->hpack_enc.n_elems == 0) { + h2c->hpack_enc.base = i; + } + + h2c->hpack_enc.last = i; + h2c->hpack_enc.top++; + h2c->hpack_enc.size += size; + h2c->hpack_enc.n_elems++; + + /* update header name lookup */ + if (*header_idx == NGX_ERROR ) { + lru = h2c->hpack_enc.top; + + for (i=0; ihpack_enc.heads[i]; + + if ( name->hash_val == 0 || (name->hash_val == key_hash + && ngx_memcmp(storage + name->pos, key, key_len) == 0) ) + { + name->hash_val = key_hash; + name->pos = h2c->hpack_enc.pos; + name->index = h2c->hpack_enc.top - 1; + break; + } + + if (lru > name->index) { + lru = name->index; + idx = i; + } + } + + if (i == HPACK_ENC_DYNAMIC_KEY_TBL_SZ) { + name = &h2c->hpack_enc.heads[idx]; + name->hash_val = hash_val; + name->pos = h2c->hpack_enc.pos; + name->index = h2c->hpack_enc.top - 1; + } + } + + ngx_memcpy(storage + h2c->hpack_enc.pos, key, key_len); + ngx_memcpy(storage + h2c->hpack_enc.pos + key_len, val, val_len); + + h2c->hpack_enc.pos += size; + if (h2c->hpack_enc.pos > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { + h2c->hpack_enc.pos = 0; + } + + return NGX_OK; +} + + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, + u_char *value, size_t value_len, + u_char *tmp) +{ + ngx_int_t idx, header_idx; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 output header: %*s: %*s", key_len, key, value_len, + value); + + /* attempt to find the value in the dynamic table */ + idx = ngx_http_v2_table_encode_strings(h2c, key_len, value_len, key, value, + &header_idx); + + if (idx > 0) { + /* positive index indicates success */ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Indexed Header Field: %ud", idx); + + *pos = 128; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), idx); + + } else { + + if (header_idx == NGX_ERROR) { /* if key is not present */ + + if (idx == NGX_ERROR) { /* if header was not added */ + *pos++ = 0; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field without" + " Indexing — New Name"); + } else { /* if header was added */ + *pos++ = 64; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field with " + "Incremental Indexing — New Name"); + } + + pos = ngx_http_v2_write_name(pos, key, key_len, tmp); + + } else { /* if key is present */ + + if (idx == NGX_ERROR) { + *pos = 0; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(4), header_idx); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field without" + " Indexing — Indexed Name: %ud", header_idx); + } else { + *pos = 64; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(6), header_idx); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field with " + "Incremental Indexing — Indexed Name: %ud", header_idx); + } + } + + pos = ngx_http_v2_write_value(pos, value, value_len, tmp); + } + + return pos; +} + + +static ngx_int_t +hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, + uint8_t *key, size_t key_len) +{ + ngx_http_v2_hpack_name_entry_t *name; + int i; + + for (i=0; ihpack_enc.heads[i]; + + if (name->hash_val == key_hash + && ngx_memcmp(h2c->hpack_enc.storage + name->pos, key, key_len) == 0) + { + if (name->index >= h2c->hpack_enc.top - h2c->hpack_enc.n_elems) { + return (h2c->hpack_enc.top - name->index) + 61; + } + break; + } + } + + return NGX_ERROR; +} + + +/* decide if a given header is present in the static dictionary, this could be + done in several ways, but it seems the fastest one is "exhaustive" search */ +static ngx_int_t +hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len) +{ + /* the static dictionary of response only headers, + although response headers can be put by origin, + that would be rare */ + static const struct { + u_char len; + const u_char val[28]; + u_char idx; + } server_headers[] = { + { 3, "age", 21},//0 + { 3, "via", 60}, + { 4, "date", 33},//2 + { 4, "etag", 34}, + { 4, "link", 45}, + { 4, "vary", 59}, + { 5, "allow", 22},//6 + { 6, "server", 54},//7 + { 7, "expires", 36},//8 + { 7, "refresh", 52}, + { 8, "location", 46},//10 + {10, "set-cookie", 55},//11 + {11, "retry-after", 53},//12 + {12, "content-type", 31},//13 + {13, "content-range", 30},//14 + {13, "accept-ranges", 18}, + {13, "cache-control", 24}, + {13, "last-modified", 44}, + {14, "content-length", 28},//18 + {16, "content-encoding", 26},//19 + {16, "content-language", 27}, + {16, "content-location", 29}, + {16, "www-authenticate", 61}, + {17, "transfer-encoding", 57},//23 + {18, "proxy-authenticate", 48},//24 + {19, "content-disposition", 25},//25 + {25, "strict-transport-security", 56},//26 + {27, "access-control-allow-origin", 20},//27 + {99, "", 99}, + }, *header; + + /* for a given length, where to start the search + since minimal length is 3, the table has a -3 + offset */ + static const int8_t start_at[] = { + [3-3] = 0, + [4-3] = 2, + [5-3] = 6, + [6-3] = 7, + [7-3] = 8, + [8-3] = 10, + [9-3] = -1, + [10-3] = 11, + [11-3] = 12, + [12-3] = 13, + [13-3] = 14, + [14-3] = 18, + [15-3] = -1, + [16-3] = 19, + [17-3] = 23, + [18-3] = 24, + [19-3] = 25, + [20-3] = -1, + [21-3] = -1, + [22-3] = -1, + [23-3] = -1, + [24-3] = -1, + [25-3] = 26, + [26-3] = -1, + [27-3] = 27, + }; + + uint64_t pref; + size_t save_len = len, i; + int8_t start; + + /* early exit for out of bounds lengths */ + if (len < 3 || len > 27) { + return NGX_ERROR; + } + + start = start_at[len - 3]; + if (start == -1) { + /* exit for non existent lengths */ + return NGX_ERROR; + } + + header = &server_headers[start_at[len - 3]]; + + /* load first 8 bytes of key, for fast comparison */ + if (len < 8) { + pref = 0; + if (len >= 4) { + pref = *(uint32_t *)(val + len - 4) | 0x20202020; + len -= 4; + } + while (len > 0) { /* 3 iterations at most */ + pref = (pref << 8) ^ (val[len - 1] | 0x20); + len--; + } + } else { + pref = *(uint64_t *)val | 0x2020202020202020; + len -= 8; + } + + /* iterate over headers with the right length */ + while (header->len == save_len) { + /* quickly compare the first 8 bytes, most tests will end here */ + if (pref != *(uint64_t *) header->val) { + header++; + continue; + } + + if (len == 0) { + /* len == 0, indicates prefix held the entire key */ + return header->idx; + } + /* for longer keys compare the rest */ + i = 1 + (save_len + 7) % 8; /* align so we can compare in quadwords */ + + while (i + 8 <= save_len) { /* 3 iterations at most */ + if ( *(uint64_t *)&header->val[i] + != (*(uint64_t *) &val[i]| 0x2020202020202020) ) + { + header++; + i = 0; + break; + } + i += 8; + } + + if (i == 0) { + continue; + } + + /* found the corresponding entry in the static dictionary */ + return header->idx; + } + + return NGX_ERROR; +} + +#else + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, + u_char *value, size_t value_len, + u_char *tmp) +{ + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 output header: %*s: %*s", key_len, key, value_len, + value); + + *pos++ = 64; + pos = ngx_http_v2_write_name(pos, key, key_len, tmp); + pos = ngx_http_v2_write_value(pos, value, value_len, tmp); + + return pos; +} + +#endif ================================================ FILE: nginx_dynamic_tls_records.patch ================================================ What we do now: We use a static record size of 4K. This gives a good balance of latency and throughput. Optimize latency: By initialy sending small (1 TCP segment) sized records, we are able to avoid HoL blocking of the first byte. This means TTFB is sometime lower by a whole RTT. Optimizing throughput: By sending increasingly larger records later in the connection, when HoL is not a problem, we reduce the overhead of TLS record (29 bytes per record with GCM/CHACHA-POLY). Logic: Start each connection with small records (1369 byte default, change with ssl_dyn_rec_size_lo). After a given number of records (40, change with ssl_dyn_rec_threshold) start sending larger records (4229, ssl_dyn_rec_size_hi). Eventually after the same number of records, start sending the largest records (ssl_buffer_size). In case the connection idles for a given amount of time (1s, ssl_dyn_rec_timeout), the process repeats itself (i.e. begin sending small records again). diff --color -uNr a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c 2025-04-16 20:01:11.000000000 +0800 +++ b/src/event/ngx_event_openssl.c 2025-04-17 02:43:15.616511714 +0800 @@ -1620,6 +1620,7 @@ sc->buffer = ((flags & NGX_SSL_BUFFER) != 0); sc->buffer_size = ssl->buffer_size; + sc->dyn_rec = ssl->dyn_rec; sc->session_ctx = ssl->ctx; @@ -2591,6 +2592,41 @@ for ( ;; ) { + /* Dynamic record resizing: + We want the initial records to fit into one TCP segment + so we don't get TCP HoL blocking due to TCP Slow Start. + A connection always starts with small records, but after + a given amount of records sent, we make the records larger + to reduce header overhead. + After a connection has idled for a given timeout, begin + the process from the start. The actual parameters are + configurable. If dyn_rec_timeout is 0, we assume dyn_rec is off. */ + + if (c->ssl->dyn_rec.timeout > 0 ) { + + if (ngx_current_msec - c->ssl->dyn_rec_last_write > + c->ssl->dyn_rec.timeout) + { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + c->ssl->dyn_rec_records_sent = 0; + + } else { + if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold * 2) + { + buf->end = buf->start + c->ssl->buffer_size; + + } else if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold) + { + buf->end = buf->start + c->ssl->dyn_rec.size_hi; + + } else { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + } + } + } + while (in && buf->last < buf->end && send < limit) { if (in->buf->last_buf || in->buf->flush) { flush = 1; @@ -2730,6 +2766,9 @@ if (n > 0) { + c->ssl->dyn_rec_records_sent++; + c->ssl->dyn_rec_last_write = ngx_current_msec; + if (c->ssl->saved_read_handler) { c->read->handler = c->ssl->saved_read_handler; diff --color -uNr a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h 2025-04-16 20:01:11.000000000 +0800 +++ b/src/event/ngx_event_openssl.h 2025-04-17 02:44:10.578945187 +0800 @@ -86,6 +86,14 @@ typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; +typedef struct { + ngx_msec_t timeout; + ngx_uint_t threshold; + size_t size_lo; + size_t size_hi; +} ngx_ssl_dyn_rec_t; + + struct ngx_ssl_s { SSL_CTX *ctx; ngx_log_t *log; @@ -95,6 +103,8 @@ ngx_rbtree_t staple_rbtree; ngx_rbtree_node_t staple_sentinel; + + ngx_ssl_dyn_rec_t dyn_rec; }; @@ -133,6 +143,10 @@ unsigned early_preread:1; unsigned write_blocked:1; unsigned sni_accepted:1; + + ngx_ssl_dyn_rec_t dyn_rec; + ngx_msec_t dyn_rec_last_write; + ngx_uint_t dyn_rec_records_sent; }; @@ -142,7 +156,7 @@ #define NGX_SSL_DFLT_BUILTIN_SCACHE -5 -#define NGX_SSL_MAX_SESSION_SIZE 8192 +#define NGX_SSL_MAX_SESSION_SIZE 16384 typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t; diff --color -uNr a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c 2025-04-16 20:01:11.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.c 2025-04-17 02:43:15.618511766 +0800 @@ -299,6 +299,41 @@ offsetof(ngx_http_ssl_srv_conf_t, reject_handshake), NULL }, + { ngx_string("ssl_dyn_rec_enable"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_enable), + NULL }, + + { ngx_string("ssl_dyn_rec_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_timeout), + NULL }, + + { ngx_string("ssl_dyn_rec_size_lo"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_lo), + NULL }, + + { ngx_string("ssl_dyn_rec_size_hi"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_hi), + NULL }, + + { ngx_string("ssl_dyn_rec_threshold"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_threshold), + NULL }, + ngx_null_command }; @@ -639,6 +674,11 @@ sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; sscf->stapling = NGX_CONF_UNSET; sscf->stapling_verify = NGX_CONF_UNSET; + sscf->dyn_rec_enable = NGX_CONF_UNSET; + sscf->dyn_rec_timeout = NGX_CONF_UNSET_MSEC; + sscf->dyn_rec_size_lo = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_size_hi = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_threshold = NGX_CONF_UNSET_UINT; return sscf; } @@ -705,6 +745,20 @@ ngx_conf_merge_str_value(conf->stapling_responder, prev->stapling_responder, ""); + ngx_conf_merge_value(conf->dyn_rec_enable, prev->dyn_rec_enable, 0); + ngx_conf_merge_msec_value(conf->dyn_rec_timeout, prev->dyn_rec_timeout, + 1000); + /* Default sizes for the dynamic record sizes are defined to fit maximal + TLS + IPv6 overhead in a single TCP segment for lo and 3 segments for hi: + 1369 = 1500 - 40 (IP) - 20 (TCP) - 10 (Time) - 61 (Max TLS overhead) */ + ngx_conf_merge_size_value(conf->dyn_rec_size_lo, prev->dyn_rec_size_lo, + 1369); + /* 4229 = (1500 - 40 - 20 - 10) * 3 - 61 */ + ngx_conf_merge_size_value(conf->dyn_rec_size_hi, prev->dyn_rec_size_hi, + 4229); + ngx_conf_merge_uint_value(conf->dyn_rec_threshold, prev->dyn_rec_threshold, + 40); + conf->ssl.log = cf->log; if (conf->certificates) { @@ -905,6 +959,28 @@ return NGX_CONF_ERROR; } + if (conf->dyn_rec_enable) { + conf->ssl.dyn_rec.timeout = conf->dyn_rec_timeout; + conf->ssl.dyn_rec.threshold = conf->dyn_rec_threshold; + + if (conf->buffer_size > conf->dyn_rec_size_lo) { + conf->ssl.dyn_rec.size_lo = conf->dyn_rec_size_lo; + + } else { + conf->ssl.dyn_rec.size_lo = conf->buffer_size; + } + + if (conf->buffer_size > conf->dyn_rec_size_hi) { + conf->ssl.dyn_rec.size_hi = conf->dyn_rec_size_hi; + + } else { + conf->ssl.dyn_rec.size_hi = conf->buffer_size; + } + + } else { + conf->ssl.dyn_rec.timeout = 0; + } + return NGX_CONF_OK; } diff --color -uNr a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h 2025-04-16 20:01:11.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.h 2025-04-17 02:43:15.618511766 +0800 @@ -64,6 +64,12 @@ ngx_flag_t stapling_verify; ngx_str_t stapling_file; ngx_str_t stapling_responder; + + ngx_flag_t dyn_rec_enable; + ngx_msec_t dyn_rec_timeout; + size_t dyn_rec_size_lo; + size_t dyn_rec_size_hi; + ngx_uint_t dyn_rec_threshold; } ngx_http_ssl_srv_conf_t; ================================================ FILE: nginx_for_1.23.4.patch ================================================ Add HTTP2 HPACK Encoding Support. Add Dynamic TLS Record Support. Using: patch -p1 < nginx_for_1.23.4.patch diff --color -uNr a/auto/modules b/auto/modules --- a/auto/modules 2021-11-02 22:49:22.000000000 +0800 +++ b/auto/modules 2021-11-04 19:41:20.976743998 +0800 @@ -423,6 +423,10 @@ . auto/module fi + if [ $HTTP_V2_HPACK_ENC = YES ]; then + have=NGX_HTTP_V2_HPACK_ENC . auto/have + fi + if :; then ngx_module_name=ngx_http_static_module ngx_module_incs= diff --color -uNr a/auto/options b/auto/options --- a/auto/options 2021-11-02 22:49:22.000000000 +0800 +++ b/auto/options 2021-11-04 19:41:20.977744024 +0800 @@ -59,6 +59,7 @@ HTTP_GZIP=YES HTTP_SSL=NO HTTP_V2=NO +HTTP_V2_HPACK_ENC=NO HTTP_SSI=YES HTTP_REALIP=NO HTTP_XSLT=NO @@ -227,6 +228,7 @@ --with-http_ssl_module) HTTP_SSL=YES ;; --with-http_v2_module) HTTP_V2=YES ;; + --with-http_v2_hpack_enc) HTTP_V2_HPACK_ENC=YES ;; --with-http_realip_module) HTTP_REALIP=YES ;; --with-http_addition_module) HTTP_ADDITION=YES ;; --with-http_xslt_module) HTTP_XSLT=YES ;; @@ -443,6 +445,7 @@ --with-http_ssl_module enable ngx_http_ssl_module --with-http_v2_module enable ngx_http_v2_module + --with-http_v2_hpack_enc enable ngx_http_v2_hpack_enc --with-http_realip_module enable ngx_http_realip_module --with-http_addition_module enable ngx_http_addition_module --with-http_xslt_module enable ngx_http_xslt_module diff --color -uNr a/src/core/ngx_murmurhash.c b/src/core/ngx_murmurhash.c --- a/src/core/ngx_murmurhash.c 2021-11-02 22:49:22.000000000 +0800 +++ b/src/core/ngx_murmurhash.c 2021-11-04 19:41:20.977744024 +0800 @@ -50,3 +50,63 @@ return h; } + + +uint64_t +ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed) +{ + uint64_t h, k; + + h = seed ^ len; + + while (len >= 8) { + k = data[0]; + k |= data[1] << 8; + k |= data[2] << 16; + k |= data[3] << 24; + k |= (uint64_t)data[4] << 32; + k |= (uint64_t)data[5] << 40; + k |= (uint64_t)data[6] << 48; + k |= (uint64_t)data[7] << 56; + + k *= 0xc6a4a7935bd1e995ull; + k ^= k >> 47; + k *= 0xc6a4a7935bd1e995ull; + + h ^= k; + h *= 0xc6a4a7935bd1e995ull; + + data += 8; + len -= 8; + } + + switch (len) { + case 7: + h ^= (uint64_t)data[6] << 48; + /* fall through */ + case 6: + h ^= (uint64_t)data[5] << 40; + /* fall through */ + case 5: + h ^= (uint64_t)data[4] << 32; + /* fall through */ + case 4: + h ^= data[3] << 24; + /* fall through */ + case 3: + h ^= data[2] << 16; + /* fall through */ + case 2: + h ^= data[1] << 8; + /* fall through */ + case 1: + h ^= data[0]; + h *= 0xc6a4a7935bd1e995ull; + } + + h ^= h >> 47; + h *= 0xc6a4a7935bd1e995ull; + h ^= h >> 47; + + return h; +} diff --color -uNr a/src/core/ngx_murmurhash.h b/src/core/ngx_murmurhash.h --- a/src/core/ngx_murmurhash.h 2021-11-02 22:49:22.000000000 +0800 +++ b/src/core/ngx_murmurhash.h 2021-11-04 19:41:20.977744024 +0800 @@ -15,5 +15,7 @@ uint32_t ngx_murmur_hash2(u_char *data, size_t len); +uint64_t ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed); + #endif /* _NGX_MURMURHASH_H_INCLUDED_ */ diff --color -uNr a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c 2021-11-02 22:49:22.000000000 +0800 +++ b/src/event/ngx_event_openssl.c 2021-11-04 19:41:20.979744075 +0800 @@ -1626,6 +1626,7 @@ sc->buffer = ((flags & NGX_SSL_BUFFER) != 0); sc->buffer_size = ssl->buffer_size; + sc->dyn_rec = ssl->dyn_rec; sc->session_ctx = ssl->ctx; @@ -2592,6 +2593,41 @@ for ( ;; ) { + /* Dynamic record resizing: + We want the initial records to fit into one TCP segment + so we don't get TCP HoL blocking due to TCP Slow Start. + A connection always starts with small records, but after + a given amount of records sent, we make the records larger + to reduce header overhead. + After a connection has idled for a given timeout, begin + the process from the start. The actual parameters are + configurable. If dyn_rec_timeout is 0, we assume dyn_rec is off. */ + + if (c->ssl->dyn_rec.timeout > 0 ) { + + if (ngx_current_msec - c->ssl->dyn_rec_last_write > + c->ssl->dyn_rec.timeout) + { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + c->ssl->dyn_rec_records_sent = 0; + + } else { + if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold * 2) + { + buf->end = buf->start + c->ssl->buffer_size; + + } else if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold) + { + buf->end = buf->start + c->ssl->dyn_rec.size_hi; + + } else { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + } + } + } + while (in && buf->last < buf->end && send < limit) { if (in->buf->last_buf || in->buf->flush) { flush = 1; @@ -2731,6 +2767,9 @@ if (n > 0) { + c->ssl->dyn_rec_records_sent++; + c->ssl->dyn_rec_last_write = ngx_current_msec; + if (c->ssl->saved_read_handler) { c->read->handler = c->ssl->saved_read_handler; diff --color -uNr a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h 2021-11-02 22:49:22.000000000 +0800 +++ b/src/event/ngx_event_openssl.h 2021-11-04 19:41:20.979744075 +0800 @@ -78,10 +78,19 @@ typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; +typedef struct { + ngx_msec_t timeout; + ngx_uint_t threshold; + size_t size_lo; + size_t size_hi; +} ngx_ssl_dyn_rec_t; + + struct ngx_ssl_s { SSL_CTX *ctx; ngx_log_t *log; size_t buffer_size; + ngx_ssl_dyn_rec_t dyn_rec; }; @@ -119,6 +128,10 @@ unsigned in_ocsp:1; unsigned early_preread:1; unsigned write_blocked:1; + + ngx_ssl_dyn_rec_t dyn_rec; + ngx_msec_t dyn_rec_last_write; + ngx_uint_t dyn_rec_records_sent; }; @@ -128,7 +141,7 @@ #define NGX_SSL_DFLT_BUILTIN_SCACHE -5 -#define NGX_SSL_MAX_SESSION_SIZE 4096 +#define NGX_SSL_MAX_SESSION_SIZE 16384 typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t; diff --color -uNr a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c 2021-11-02 22:49:22.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.c 2021-11-04 19:41:20.980744101 +0800 @@ -296,6 +296,41 @@ offsetof(ngx_http_ssl_srv_conf_t, reject_handshake), NULL }, + { ngx_string("ssl_dyn_rec_enable"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_enable), + NULL }, + + { ngx_string("ssl_dyn_rec_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_timeout), + NULL }, + + { ngx_string("ssl_dyn_rec_size_lo"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_lo), + NULL }, + + { ngx_string("ssl_dyn_rec_size_hi"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_hi), + NULL }, + + { ngx_string("ssl_dyn_rec_threshold"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_threshold), + NULL }, + ngx_null_command }; @@ -595,6 +630,11 @@ sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; sscf->stapling = NGX_CONF_UNSET; sscf->stapling_verify = NGX_CONF_UNSET; + sscf->dyn_rec_enable = NGX_CONF_UNSET; + sscf->dyn_rec_timeout = NGX_CONF_UNSET_MSEC; + sscf->dyn_rec_size_lo = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_size_hi = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_threshold = NGX_CONF_UNSET_UINT; return sscf; } @@ -670,6 +710,20 @@ ngx_conf_merge_str_value(conf->stapling_responder, prev->stapling_responder, ""); + ngx_conf_merge_value(conf->dyn_rec_enable, prev->dyn_rec_enable, 0); + ngx_conf_merge_msec_value(conf->dyn_rec_timeout, prev->dyn_rec_timeout, + 1000); + /* Default sizes for the dynamic record sizes are defined to fit maximal + TLS + IPv6 overhead in a single TCP segment for lo and 3 segments for hi: + 1369 = 1500 - 40 (IP) - 20 (TCP) - 10 (Time) - 61 (Max TLS overhead) */ + ngx_conf_merge_size_value(conf->dyn_rec_size_lo, prev->dyn_rec_size_lo, + 1369); + /* 4229 = (1500 - 40 - 20 - 10) * 3 - 61 */ + ngx_conf_merge_size_value(conf->dyn_rec_size_hi, prev->dyn_rec_size_hi, + 4229); + ngx_conf_merge_uint_value(conf->dyn_rec_threshold, prev->dyn_rec_threshold, + 40); + conf->ssl.log = cf->log; if (conf->enable) { @@ -896,6 +950,28 @@ return NGX_CONF_ERROR; } + if (conf->dyn_rec_enable) { + conf->ssl.dyn_rec.timeout = conf->dyn_rec_timeout; + conf->ssl.dyn_rec.threshold = conf->dyn_rec_threshold; + + if (conf->buffer_size > conf->dyn_rec_size_lo) { + conf->ssl.dyn_rec.size_lo = conf->dyn_rec_size_lo; + + } else { + conf->ssl.dyn_rec.size_lo = conf->buffer_size; + } + + if (conf->buffer_size > conf->dyn_rec_size_hi) { + conf->ssl.dyn_rec.size_hi = conf->dyn_rec_size_hi; + + } else { + conf->ssl.dyn_rec.size_hi = conf->buffer_size; + } + + } else { + conf->ssl.dyn_rec.timeout = 0; + } + return NGX_CONF_OK; } diff --color -uNr a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h 2021-11-02 22:49:22.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.h 2021-11-04 19:41:20.981744126 +0800 @@ -67,6 +67,12 @@ u_char *file; ngx_uint_t line; + + ngx_flag_t dyn_rec_enable; + ngx_msec_t dyn_rec_timeout; + size_t dyn_rec_size_lo; + size_t dyn_rec_size_hi; + ngx_uint_t dyn_rec_threshold; } ngx_http_ssl_srv_conf_t; diff --color -uNr a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c --- a/src/http/v2/ngx_http_v2.c 2021-11-02 22:49:22.000000000 +0800 +++ b/src/http/v2/ngx_http_v2.c 2021-11-04 19:41:20.982744152 +0800 @@ -274,6 +274,8 @@ h2c->frame_size = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + h2c->max_hpack_table_size = NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE; + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); h2c->concurrent_pushes = h2scf->concurrent_pushes; @@ -2283,6 +2285,14 @@ case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING: h2c->table_update = 1; + + if (value > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { + h2c->max_hpack_table_size = NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE; + } else { + h2c->max_hpack_table_size = value; + } + + h2c->indicate_resize = 1; break; default: diff --color -uNr a/src/http/v2/ngx_http_v2_encode.c b/src/http/v2/ngx_http_v2_encode.c --- a/src/http/v2/ngx_http_v2_encode.c 2021-11-02 22:49:22.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_encode.c 2021-11-04 19:41:20.983744177 +0800 @@ -10,7 +10,7 @@ #include -static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, +u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); @@ -40,7 +40,7 @@ } -static u_char * +u_char * ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) { if (value < prefix) { diff --color -uNr a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c --- a/src/http/v2/ngx_http_v2_filter_module.c 2021-11-02 22:49:22.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_filter_module.c 2021-11-04 19:41:20.984744203 +0800 @@ -23,10 +23,53 @@ #define ngx_http_v2_literal_size(h) \ (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1) +#define ngx_http_v2_indexed(i) (128 + (i)) +#define ngx_http_v2_inc_indexed(i) (64 + (i)) + +#define NGX_HTTP_V2_ENCODE_RAW 0 +#define NGX_HTTP_V2_ENCODE_HUFF 0x80 + +#define NGX_HTTP_V2_AUTHORITY_INDEX 1 +#define NGX_HTTP_V2_METHOD_GET_INDEX 2 +#define NGX_HTTP_V2_PATH_INDEX 4 + +#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6 +#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7 + +#define NGX_HTTP_V2_STATUS_INDEX 8 +#define NGX_HTTP_V2_STATUS_200_INDEX 8 +#define NGX_HTTP_V2_STATUS_204_INDEX 9 +#define NGX_HTTP_V2_STATUS_206_INDEX 10 +#define NGX_HTTP_V2_STATUS_304_INDEX 11 +#define NGX_HTTP_V2_STATUS_400_INDEX 12 +#define NGX_HTTP_V2_STATUS_404_INDEX 13 +#define NGX_HTTP_V2_STATUS_500_INDEX 14 + +#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 +#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 +#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 +#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 +#define NGX_HTTP_V2_DATE_INDEX 33 +#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 +#define NGX_HTTP_V2_LOCATION_INDEX 46 +#define NGX_HTTP_V2_SERVER_INDEX 54 +#define NGX_HTTP_V2_USER_AGENT_INDEX 58 +#define NGX_HTTP_V2_VARY_INDEX 59 #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 +static const struct { + u_char *name; + u_char const len; +} push_header[] = { + { (u_char*)":authority" , 10 }, + { (u_char*)"accept-encoding" , 15 }, + { (u_char*)"accept-language" , 15 }, + { (u_char*)"user-agent" , 10 } +}; + + typedef struct { ngx_str_t name; u_char index; @@ -155,11 +198,9 @@ #endif static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER); - static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)]; static size_t nginx_ver_build_len = ngx_http_v2_literal_size(NGINX_VER_BUILD); - static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)]; stream = r->stream; @@ -435,7 +476,7 @@ } tmp = ngx_palloc(r->pool, tmp_len); - pos = ngx_pnalloc(r->pool, len); + pos = ngx_pnalloc(r->pool, len + 15 + 1); if (pos == NULL || tmp == NULL) { return NGX_ERROR; @@ -443,11 +484,16 @@ start = pos; - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; + h2c = r->stream->connection; + + if (h2c->indicate_resize) { + *pos = 32; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), + h2c->max_hpack_table_size); + h2c->indicate_resize = 0; +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_table_resize(h2c); +#endif } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, @@ -458,67 +504,28 @@ *pos++ = status; } else { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); - *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3; - pos = ngx_sprintf(pos, "%03ui", r->headers_out.status); + ngx_sprintf(pos + 8, "%O3ui", r->headers_out.status); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)":status", + sizeof(":status") - 1, pos + 8, 3, tmp); } if (r->headers_out.server == NULL) { - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: %s\"", - NGINX_VER); - - } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: %s\"", - NGINX_VER_BUILD); - - } else { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: nginx\""); - } - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); - - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - if (nginx_ver[0] == '\0') { - p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER, - sizeof(NGINX_VER) - 1, tmp); - nginx_ver_len = p - nginx_ver; - } - - pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len); + pos = ngx_http_v2_write_header_str("server", NGINX_VER); } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - if (nginx_ver_build[0] == '\0') { - p = ngx_http_v2_write_value(nginx_ver_build, - (u_char *) NGINX_VER_BUILD, - sizeof(NGINX_VER_BUILD) - 1, tmp); - nginx_ver_build_len = p - nginx_ver_build; - } - - pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len); + pos = ngx_http_v2_write_header_str("server", NGINX_VER_BUILD); } else { - pos = ngx_cpymem(pos, nginx, sizeof(nginx)); + pos = ngx_http_v2_write_header_str("server", "nginx"); } } if (r->headers_out.date == NULL) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"date: %V\"", - &ngx_cached_http_time); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); - pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data, - ngx_cached_http_time.len, tmp); + pos = ngx_http_v2_write_header_tbl("date", ngx_cached_http_time); } if (r->headers_out.content_type.len) { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); - if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { @@ -544,64 +551,36 @@ r->headers_out.content_type.data = p - len; } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"content-type: %V\"", - &r->headers_out.content_type); - - pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data, - r->headers_out.content_type.len, tmp); + pos = ngx_http_v2_write_header_tbl("content-type", + r->headers_out.content_type); } if (r->headers_out.content_length == NULL && r->headers_out.content_length_n >= 0) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"content-length: %O\"", - r->headers_out.content_length_n); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX); - - p = pos; - pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n); - *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1); + p = ngx_sprintf(pos + 15, "%O", r->headers_out.content_length_n); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"content-length", + sizeof("content-length") - 1, pos + 15, + p - (pos + 15), tmp); } if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); - - ngx_http_time(pos, r->headers_out.last_modified_time); + ngx_http_time(pos + 14, r->headers_out.last_modified_time); len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"last-modified: %*s\"", - len, pos); - - /* - * Date will always be encoded using huffman in the temporary buffer, - * so it's safe here to use src and dst pointing to the same address. - */ - pos = ngx_http_v2_write_value(pos, pos, len, tmp); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"last-modified", + sizeof("last-modified") - 1, pos + 14, + len, tmp); } if (r->headers_out.location && r->headers_out.location->value.len) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"location: %V\"", - &r->headers_out.location->value); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); - pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data, - r->headers_out.location->value.len, tmp); + pos = ngx_http_v2_write_header_tbl("location", r->headers_out.location->value); } #if (NGX_HTTP_GZIP) if (r->gzip_vary) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"vary: Accept-Encoding\""); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); - pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding)); + pos = ngx_http_v2_write_header_str("vary", "Accept-Encoding"); } #endif @@ -624,23 +603,10 @@ continue; } -#if (NGX_DEBUG) - if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { - ngx_strlow(tmp, header[i].key.data, header[i].key.len); - - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"%*s: %V\"", - header[i].key.len, tmp, &header[i].value); - } -#endif - - *pos++ = 0; - - pos = ngx_http_v2_write_name(pos, header[i].key.data, - header[i].key.len, tmp); + pos = ngx_http_v2_write_header(h2c, pos, header[i].key.data, + header[i].key.len, header[i].value.data, + header[i].value.len, tmp); - pos = ngx_http_v2_write_value(pos, header[i].value.data, - header[i].value.len, tmp); } fin = r->header_only @@ -998,6 +964,7 @@ for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { len += binary[i].len; + len += push_header[i].len + 1; } pos = ngx_pnalloc(r->pool, len); @@ -1007,12 +974,17 @@ start = pos; - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; - } + h2c = r->stream->connection; + + if (h2c->indicate_resize) { + *pos = 32; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), + h2c->max_hpack_table_size); + h2c->indicate_resize = 0; +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_table_resize(h2c); +#endif + } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":method: GET\""); @@ -1022,8 +994,7 @@ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":path: %V\"", path); - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); - pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp); + pos = ngx_http_v2_write_header_pot(":path", path); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":scheme: %V\"", &r->schema); @@ -1048,11 +1019,15 @@ continue; } + value = &(*h)->value; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \"%V: %V\"", &ph[i].name, &(*h)->value); - pos = ngx_cpymem(pos, binary[i].data, binary[i].len); + pos = ngx_http_v2_write_header(h2c, pos, + push_header[i].name, push_header[i].len, value->data, value->len, + tmp); } frame = ngx_http_v2_create_push_frame(r, start, pos); diff --color -uNr a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h --- a/src/http/v2/ngx_http_v2.h 2021-11-02 22:49:22.000000000 +0800 +++ b/src/http/v2/ngx_http_v2.h 2021-11-04 19:41:20.985744228 +0800 @@ -51,6 +51,14 @@ #define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1) #define NGX_HTTP_V2_DEFAULT_WINDOW 65535 +#define HPACK_ENC_HTABLE_SZ 128 /* better to keep a PoT < 64k */ +#define HPACK_ENC_HTABLE_ENTRIES ((HPACK_ENC_HTABLE_SZ * 100) / 128) +#define HPACK_ENC_DYNAMIC_KEY_TBL_SZ 10 /* 10 is sufficient for most */ +#define HPACK_ENC_MAX_ENTRY 512 /* longest header size to match */ + +#define NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE 4096 +#define NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE 16384 /* < 64k */ + #define NGX_HTTP_V2_DEFAULT_WEIGHT 16 @@ -114,6 +122,46 @@ } ngx_http_v2_hpack_t; +#if (NGX_HTTP_V2_HPACK_ENC) +typedef struct { + uint64_t hash_val; + uint32_t index; + uint16_t pos; + uint16_t klen, vlen; + uint16_t size; + uint16_t next; +} ngx_http_v2_hpack_enc_entry_t; + + +typedef struct { + uint64_t hash_val; + uint32_t index; + uint16_t pos; + uint16_t klen; +} ngx_http_v2_hpack_name_entry_t; + + +typedef struct { + size_t size; /* size as defined in RFC 7541 */ + uint32_t top; /* the last entry */ + uint32_t pos; + uint16_t n_elems; /* number of elements */ + uint16_t base; /* index of the oldest entry */ + uint16_t last; /* index of the newest entry */ + + /* hash table for dynamic entries, instead using a generic hash table, + which would be too slow to process a significant amount of headers, + this table is not determenistic, and might ocasionally fail to insert + a value, at the cost of slightly worse compression, but significantly + faster performance */ + ngx_http_v2_hpack_enc_entry_t htable[HPACK_ENC_HTABLE_SZ]; + ngx_http_v2_hpack_name_entry_t heads[HPACK_ENC_DYNAMIC_KEY_TBL_SZ]; + u_char storage[NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE + + HPACK_ENC_MAX_ENTRY]; +} ngx_http_v2_hpack_enc_t; +#endif + + struct ngx_http_v2_connection_s { ngx_connection_t *connection; ngx_http_connection_t *http_connection; @@ -135,6 +183,8 @@ size_t frame_size; + size_t max_hpack_table_size; + ngx_queue_t waiting; ngx_http_v2_state_t state; @@ -164,6 +214,11 @@ unsigned blocked:1; unsigned goaway:1; unsigned push_disabled:1; + unsigned indicate_resize:1; + +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_hpack_enc_t hpack_enc; +#endif }; @@ -207,6 +262,8 @@ ngx_array_t *cookies; + size_t header_limit; + ngx_pool_t *pool; unsigned waiting:1; @@ -419,4 +476,35 @@ u_char *tmp, ngx_uint_t lower); +u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, + u_char *tmp, ngx_uint_t lower); + +u_char * +ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); + +#define ngx_http_v2_write_name(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 1) +#define ngx_http_v2_write_value(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 0) + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, u_char *value, size_t value_len, + u_char *tmp); + +void +ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c); + +#define ngx_http_v2_write_header_str(key, value) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + (u_char *) value, sizeof(value) - 1, tmp); + +#define ngx_http_v2_write_header_tbl(key, val) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + val.data, val.len, tmp); + +#define ngx_http_v2_write_header_pot(key, val) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + val->data, val->len, tmp); + #endif /* _NGX_HTTP_V2_H_INCLUDED_ */ diff --color -uNr a/src/http/v2/ngx_http_v2_table.c b/src/http/v2/ngx_http_v2_table.c --- a/src/http/v2/ngx_http_v2_table.c 2021-11-02 22:49:22.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_table.c 2021-11-04 19:41:20.986744254 +0800 @@ -361,3 +361,434 @@ return NGX_OK; } + + +#if (NGX_HTTP_V2_HPACK_ENC) + +static ngx_int_t +hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len); + +static ngx_int_t +hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, + uint8_t *key, size_t key_len); + + +void +ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c) +{ + ngx_http_v2_hpack_enc_entry_t *table; + uint64_t idx; + + table = h2c->hpack_enc.htable; + + while (h2c->hpack_enc.size > h2c->max_hpack_table_size) { + idx = h2c->hpack_enc.base; + h2c->hpack_enc.base = table[idx].next; + h2c->hpack_enc.size -= table[idx].size; + table[idx].hash_val = 0; + h2c->hpack_enc.n_elems--; + } +} + + +/* checks if a header is in the hpack table - if so returns the table entry, + otherwise encodes and inserts into the table and returns 0, + if failed to insert into table, returns -1 */ +static ngx_int_t +ngx_http_v2_table_encode_strings(ngx_http_v2_connection_t *h2c, + size_t key_len, size_t val_len, uint8_t *key, uint8_t *val, + ngx_int_t *header_idx) +{ + uint64_t hash_val, key_hash, idx, lru; + int i; + size_t size = key_len + val_len + 32; + uint8_t *storage = h2c->hpack_enc.storage; + + ngx_http_v2_hpack_enc_entry_t *table; + ngx_http_v2_hpack_name_entry_t *name; + + *header_idx = NGX_ERROR; + /* step 1: compute the hash value of header */ + if (size > HPACK_ENC_MAX_ENTRY || size > h2c->max_hpack_table_size) { + return NGX_ERROR; + } + + key_hash = ngx_murmur_hash2_64(key, key_len, 0x01234); + hash_val = ngx_murmur_hash2_64(val, val_len, key_hash); + + if (hash_val == 0) { + return NGX_ERROR; + } + + /* step 2: check if full header in the table */ + idx = hash_val; + i = -1; + while (idx) { + /* at most 8 locations are checked, but most will be done in 1 or 2 */ + table = &h2c->hpack_enc.htable[idx % HPACK_ENC_HTABLE_SZ]; + if (table->hash_val == hash_val + && table->klen == key_len + && table->vlen == val_len + && ngx_memcmp(key, storage + table->pos, key_len) == 0 + && ngx_memcmp(val, storage + table->pos + key_len, val_len) == 0) + { + return (h2c->hpack_enc.top - table->index) + 61; + } + + if (table->hash_val == 0 && i == -1) { + i = idx % HPACK_ENC_HTABLE_SZ; + break; + } + + idx >>= 8; + } + + /* step 3: check if key is in one of the tables */ + *header_idx = hpack_get_static_index(h2c, key, key_len); + + if (i == -1) { + return NGX_ERROR; + } + + if (*header_idx == NGX_ERROR) { + *header_idx = hpack_get_dynamic_index(h2c, key_hash, key, key_len); + } + + /* step 4: store the new entry */ + table = h2c->hpack_enc.htable; + + if (h2c->hpack_enc.top == 0xffffffff) { + /* just to be on the safe side, avoid overflow */ + ngx_memset(&h2c->hpack_enc, 0, sizeof(ngx_http_v2_hpack_enc_t)); + } + + while ((h2c->hpack_enc.size + size > h2c->max_hpack_table_size) + || h2c->hpack_enc.n_elems == HPACK_ENC_HTABLE_ENTRIES) { + /* make space for the new entry first */ + idx = h2c->hpack_enc.base; + h2c->hpack_enc.base = table[idx].next; + h2c->hpack_enc.size -= table[idx].size; + table[idx].hash_val = 0; + h2c->hpack_enc.n_elems--; + } + + table[i] = (ngx_http_v2_hpack_enc_entry_t){.hash_val = hash_val, + .index = h2c->hpack_enc.top, + .pos = h2c->hpack_enc.pos, + .klen = key_len, + .vlen = val_len, + .size = size, + .next = 0}; + + table[h2c->hpack_enc.last].next = i; + if (h2c->hpack_enc.n_elems == 0) { + h2c->hpack_enc.base = i; + } + + h2c->hpack_enc.last = i; + h2c->hpack_enc.top++; + h2c->hpack_enc.size += size; + h2c->hpack_enc.n_elems++; + + /* update header name lookup */ + if (*header_idx == NGX_ERROR ) { + lru = h2c->hpack_enc.top; + + for (i=0; ihpack_enc.heads[i]; + + if ( name->hash_val == 0 || (name->hash_val == key_hash + && ngx_memcmp(storage + name->pos, key, key_len) == 0) ) + { + name->hash_val = key_hash; + name->pos = h2c->hpack_enc.pos; + name->index = h2c->hpack_enc.top - 1; + break; + } + + if (lru > name->index) { + lru = name->index; + idx = i; + } + } + + if (i == HPACK_ENC_DYNAMIC_KEY_TBL_SZ) { + name = &h2c->hpack_enc.heads[idx]; + name->hash_val = hash_val; + name->pos = h2c->hpack_enc.pos; + name->index = h2c->hpack_enc.top - 1; + } + } + + ngx_memcpy(storage + h2c->hpack_enc.pos, key, key_len); + ngx_memcpy(storage + h2c->hpack_enc.pos + key_len, val, val_len); + + h2c->hpack_enc.pos += size; + if (h2c->hpack_enc.pos > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { + h2c->hpack_enc.pos = 0; + } + + return NGX_OK; +} + + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, + u_char *value, size_t value_len, + u_char *tmp) +{ + ngx_int_t idx, header_idx; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 output header: %*s: %*s", key_len, key, value_len, + value); + + /* attempt to find the value in the dynamic table */ + idx = ngx_http_v2_table_encode_strings(h2c, key_len, value_len, key, value, + &header_idx); + + if (idx > 0) { + /* positive index indicates success */ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Indexed Header Field: %ud", idx); + + *pos = 128; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), idx); + + } else { + + if (header_idx == NGX_ERROR) { /* if key is not present */ + + if (idx == NGX_ERROR) { /* if header was not added */ + *pos++ = 0; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field without" + " Indexing — New Name"); + } else { /* if header was added */ + *pos++ = 64; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field with " + "Incremental Indexing — New Name"); + } + + pos = ngx_http_v2_write_name(pos, key, key_len, tmp); + + } else { /* if key is present */ + + if (idx == NGX_ERROR) { + *pos = 0; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(4), header_idx); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field without" + " Indexing — Indexed Name: %ud", header_idx); + } else { + *pos = 64; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(6), header_idx); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field with " + "Incremental Indexing — Indexed Name: %ud", header_idx); + } + } + + pos = ngx_http_v2_write_value(pos, value, value_len, tmp); + } + + return pos; +} + + +static ngx_int_t +hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, + uint8_t *key, size_t key_len) +{ + ngx_http_v2_hpack_name_entry_t *name; + int i; + + for (i=0; ihpack_enc.heads[i]; + + if (name->hash_val == key_hash + && ngx_memcmp(h2c->hpack_enc.storage + name->pos, key, key_len) == 0) + { + if (name->index >= h2c->hpack_enc.top - h2c->hpack_enc.n_elems) { + return (h2c->hpack_enc.top - name->index) + 61; + } + break; + } + } + + return NGX_ERROR; +} + + +/* decide if a given header is present in the static dictionary, this could be + done in several ways, but it seems the fastest one is "exhaustive" search */ +static ngx_int_t +hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len) +{ + /* the static dictionary of response only headers, + although response headers can be put by origin, + that would be rare */ + static const struct { + u_char len; + const u_char val[28]; + u_char idx; + } server_headers[] = { + { 3, "age", 21},//0 + { 3, "via", 60}, + { 4, "date", 33},//2 + { 4, "etag", 34}, + { 4, "link", 45}, + { 4, "vary", 59}, + { 5, "allow", 22},//6 + { 6, "server", 54},//7 + { 7, "expires", 36},//8 + { 7, "refresh", 52}, + { 8, "location", 46},//10 + {10, "set-cookie", 55},//11 + {11, "retry-after", 53},//12 + {12, "content-type", 31},//13 + {13, "content-range", 30},//14 + {13, "accept-ranges", 18}, + {13, "cache-control", 24}, + {13, "last-modified", 44}, + {14, "content-length", 28},//18 + {16, "content-encoding", 26},//19 + {16, "content-language", 27}, + {16, "content-location", 29}, + {16, "www-authenticate", 61}, + {17, "transfer-encoding", 57},//23 + {18, "proxy-authenticate", 48},//24 + {19, "content-disposition", 25},//25 + {25, "strict-transport-security", 56},//26 + {27, "access-control-allow-origin", 20},//27 + {99, "", 99}, + }, *header; + + /* for a given length, where to start the search + since minimal length is 3, the table has a -3 + offset */ + static const int8_t start_at[] = { + [3-3] = 0, + [4-3] = 2, + [5-3] = 6, + [6-3] = 7, + [7-3] = 8, + [8-3] = 10, + [9-3] = -1, + [10-3] = 11, + [11-3] = 12, + [12-3] = 13, + [13-3] = 14, + [14-3] = 18, + [15-3] = -1, + [16-3] = 19, + [17-3] = 23, + [18-3] = 24, + [19-3] = 25, + [20-3] = -1, + [21-3] = -1, + [22-3] = -1, + [23-3] = -1, + [24-3] = -1, + [25-3] = 26, + [26-3] = -1, + [27-3] = 27, + }; + + uint64_t pref; + size_t save_len = len, i; + int8_t start; + + /* early exit for out of bounds lengths */ + if (len < 3 || len > 27) { + return NGX_ERROR; + } + + start = start_at[len - 3]; + if (start == -1) { + /* exit for non existent lengths */ + return NGX_ERROR; + } + + header = &server_headers[start_at[len - 3]]; + + /* load first 8 bytes of key, for fast comparison */ + if (len < 8) { + pref = 0; + if (len >= 4) { + pref = *(uint32_t *)(val + len - 4) | 0x20202020; + len -= 4; + } + while (len > 0) { /* 3 iterations at most */ + pref = (pref << 8) ^ (val[len - 1] | 0x20); + len--; + } + } else { + pref = *(uint64_t *)val | 0x2020202020202020; + len -= 8; + } + + /* iterate over headers with the right length */ + while (header->len == save_len) { + /* quickly compare the first 8 bytes, most tests will end here */ + if (pref != *(uint64_t *) header->val) { + header++; + continue; + } + + if (len == 0) { + /* len == 0, indicates prefix held the entire key */ + return header->idx; + } + /* for longer keys compare the rest */ + i = 1 + (save_len + 7) % 8; /* align so we can compare in quadwords */ + + while (i + 8 <= save_len) { /* 3 iterations at most */ + if ( *(uint64_t *)&header->val[i] + != (*(uint64_t *) &val[i]| 0x2020202020202020) ) + { + header++; + i = 0; + break; + } + i += 8; + } + + if (i == 0) { + continue; + } + + /* found the corresponding entry in the static dictionary */ + return header->idx; + } + + return NGX_ERROR; +} + +#else + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, + u_char *value, size_t value_len, + u_char *tmp) +{ + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 output header: %*s: %*s", key_len, key, value_len, + value); + + *pos++ = 64; + pos = ngx_http_v2_write_name(pos, key, key_len, tmp); + pos = ngx_http_v2_write_value(pos, value, value_len, tmp); + + return pos; +} + +#endif ================================================ FILE: nginx_io_uring.patch ================================================ From a052b2d1adf583233454a6d7f89963544714ff58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Fri, 14 Aug 2020 10:44:44 +0800 Subject: [PATCH] Add io_uring support --- auto/unix | 39 ++--- src/core/ngx_open_file_cache.c | 6 +- src/event/modules/ngx_epoll_module.c | 225 ++++++--------------------- src/event/ngx_event.h | 4 +- src/os/unix/ngx_linux_aio_read.c | 63 +++++--- src/os/unix/ngx_linux_config.h | 4 - 6 files changed, 98 insertions(+), 243 deletions(-) diff --git a/auto/unix b/auto/unix index ff9697a..2e2f63a 100644 --- a/auto/unix +++ b/auto/unix @@ -532,44 +532,23 @@ if [ $NGX_FILE_AIO = YES ]; then if [ $ngx_found = no ]; then - ngx_feature="Linux AIO support" + ngx_feature="Linux io_uring support (liburing)" ngx_feature_name="NGX_HAVE_FILE_AIO" ngx_feature_run=no - ngx_feature_incs="#include - #include " + ngx_feature_incs="#include " ngx_feature_path= - ngx_feature_libs= - ngx_feature_test="struct iocb iocb; - iocb.aio_lio_opcode = IOCB_CMD_PREAD; - iocb.aio_flags = IOCB_FLAG_RESFD; - iocb.aio_resfd = -1; - (void) iocb; - (void) eventfd(0, 0)" + ngx_feature_libs="-luring" + ngx_feature_test="struct io_uring ring; + int ret = io_uring_queue_init(64, &ring, 0); + if (ret < 0) return 1; + io_uring_queue_exit(&ring);" . auto/feature if [ $ngx_found = yes ]; then have=NGX_HAVE_EVENTFD . auto/have have=NGX_HAVE_SYS_EVENTFD_H . auto/have CORE_SRCS="$CORE_SRCS $LINUX_AIO_SRCS" - fi - fi - - if [ $ngx_found = no ]; then - - ngx_feature="Linux AIO support (SYS_eventfd)" - ngx_feature_incs="#include - #include " - ngx_feature_test="struct iocb iocb; - iocb.aio_lio_opcode = IOCB_CMD_PREAD; - iocb.aio_flags = IOCB_FLAG_RESFD; - iocb.aio_resfd = -1; - (void) iocb; - (void) SYS_eventfd" - . auto/feature - - if [ $ngx_found = yes ]; then - have=NGX_HAVE_EVENTFD . auto/have - CORE_SRCS="$CORE_SRCS $LINUX_AIO_SRCS" + CORE_LIBS="$CORE_LIBS -luring" fi fi @@ -577,7 +556,7 @@ if [ $NGX_FILE_AIO = YES ]; then cat << END $0: no supported file AIO was found -Currently file AIO is supported on FreeBSD 4.3+ and Linux 2.6.22+ only +Currently file AIO is supported on FreeBSD 4.3+ and Linux 5.1.0+ (requires liburing) only END exit 1 diff --git a/src/core/ngx_open_file_cache.c b/src/core/ngx_open_file_cache.c index b23ee78..682d48c 100644 --- a/src/core/ngx_open_file_cache.c +++ b/src/core/ngx_open_file_cache.c @@ -869,11 +869,11 @@ ngx_open_and_stat_file(ngx_str_t *name, ngx_open_file_info_t *of, if (!of->log) { /* - * Use non-blocking open() not to hang on FIFO files, etc. - * This flag has no effect on a regular files. + * Differs from plain read, IORING_OP_READV with O_NONBLOCK + * will return -EAGAIN if the operation may block. */ - fd = ngx_open_file_wrapper(name, of, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK, + fd = ngx_open_file_wrapper(name, of, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0, log); } else { diff --git a/src/event/modules/ngx_epoll_module.c b/src/event/modules/ngx_epoll_module.c index 98e3ce7..09dda1d 100644 --- a/src/event/modules/ngx_epoll_module.c +++ b/src/event/modules/ngx_epoll_module.c @@ -9,6 +9,10 @@ #include #include +#if (NGX_HAVE_FILE_AIO) +#include +#endif + #if (NGX_TEST_BUILD_EPOLL) @@ -75,23 +79,6 @@ int epoll_wait(int epfd, struct epoll_event *events, int nevents, int timeout) #define SYS_eventfd 323 #endif -#if (NGX_HAVE_FILE_AIO) - -#define SYS_io_setup 245 -#define SYS_io_destroy 246 -#define SYS_io_getevents 247 - -typedef u_int aio_context_t; - -struct io_event { - uint64_t data; /* the data field from the iocb */ - uint64_t obj; /* what iocb this event came from */ - int64_t res; /* result code for this event */ - int64_t res2; /* secondary result */ -}; - - -#endif #endif /* NGX_TEST_BUILD_EPOLL */ @@ -124,7 +111,7 @@ static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags); #if (NGX_HAVE_FILE_AIO) -static void ngx_epoll_eventfd_handler(ngx_event_t *ev); +static void ngx_epoll_io_uring_handler(ngx_event_t *ev); #endif static void *ngx_epoll_create_conf(ngx_cycle_t *cycle); @@ -141,13 +128,11 @@ static ngx_connection_t notify_conn; #endif #if (NGX_HAVE_FILE_AIO) +struct io_uring ngx_ring; +struct io_uring_params ngx_ring_params; -int ngx_eventfd = -1; -aio_context_t ngx_aio_ctx = 0; - -static ngx_event_t ngx_eventfd_event; -static ngx_connection_t ngx_eventfd_conn; - +static ngx_event_t ngx_ring_event; +static ngx_connection_t ngx_ring_conn; #endif #if (NGX_HAVE_EPOLLRDHUP) @@ -217,102 +202,40 @@ ngx_module_t ngx_epoll_module = { #if (NGX_HAVE_FILE_AIO) -/* - * We call io_setup(), io_destroy() io_submit(), and io_getevents() directly - * as syscalls instead of libaio usage, because the library header file - * supports eventfd() since 0.3.107 version only. - */ - -static int -io_setup(u_int nr_reqs, aio_context_t *ctx) -{ - return syscall(SYS_io_setup, nr_reqs, ctx); -} - - -static int -io_destroy(aio_context_t ctx) -{ - return syscall(SYS_io_destroy, ctx); -} - - -static int -io_getevents(aio_context_t ctx, long min_nr, long nr, struct io_event *events, - struct timespec *tmo) -{ - return syscall(SYS_io_getevents, ctx, min_nr, nr, events, tmo); -} - - static void ngx_epoll_aio_init(ngx_cycle_t *cycle, ngx_epoll_conf_t *epcf) { - int n; struct epoll_event ee; -#if (NGX_HAVE_SYS_EVENTFD_H) - ngx_eventfd = eventfd(0, 0); -#else - ngx_eventfd = syscall(SYS_eventfd, 0); -#endif - - if (ngx_eventfd == -1) { - ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, - "eventfd() failed"); - ngx_file_aio = 0; - return; - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, - "eventfd: %d", ngx_eventfd); - - n = 1; - - if (ioctl(ngx_eventfd, FIONBIO, &n) == -1) { - ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, - "ioctl(eventfd, FIONBIO) failed"); - goto failed; - } - - if (io_setup(epcf->aio_requests, &ngx_aio_ctx) == -1) { + if (io_uring_queue_init_params(64, &ngx_ring, &ngx_ring_params) < 0) { ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, - "io_setup() failed"); + "io_uring_queue_init_params() failed"); goto failed; } - ngx_eventfd_event.data = &ngx_eventfd_conn; - ngx_eventfd_event.handler = ngx_epoll_eventfd_handler; - ngx_eventfd_event.log = cycle->log; - ngx_eventfd_event.active = 1; - ngx_eventfd_conn.fd = ngx_eventfd; - ngx_eventfd_conn.read = &ngx_eventfd_event; - ngx_eventfd_conn.log = cycle->log; + ngx_ring_event.data = &ngx_ring_conn; + ngx_ring_event.handler = ngx_epoll_io_uring_handler; + ngx_ring_event.log = cycle->log; + ngx_ring_event.active = 1; + ngx_ring_conn.fd = ngx_ring.ring_fd; + ngx_ring_conn.read = &ngx_ring_event; + ngx_ring_conn.log = cycle->log; ee.events = EPOLLIN|EPOLLET; - ee.data.ptr = &ngx_eventfd_conn; + ee.data.ptr = &ngx_ring_conn; - if (epoll_ctl(ep, EPOLL_CTL_ADD, ngx_eventfd, &ee) != -1) { + if (epoll_ctl(ep, EPOLL_CTL_ADD, ngx_ring.ring_fd, &ee) != -1) { return; } ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, "epoll_ctl(EPOLL_CTL_ADD, eventfd) failed"); - if (io_destroy(ngx_aio_ctx) == -1) { - ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, - "io_destroy() failed"); - } + io_uring_queue_exit(&ngx_ring); failed: - if (close(ngx_eventfd) == -1) { - ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, - "eventfd close() failed"); - } - - ngx_eventfd = -1; - ngx_aio_ctx = 0; + ngx_ring.ring_fd = 0; ngx_file_aio = 0; } @@ -549,23 +472,11 @@ ngx_epoll_done(ngx_cycle_t *cycle) #if (NGX_HAVE_FILE_AIO) - if (ngx_eventfd != -1) { - - if (io_destroy(ngx_aio_ctx) == -1) { - ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, - "io_destroy() failed"); - } - - if (close(ngx_eventfd) == -1) { - ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, - "eventfd close() failed"); - } - - ngx_eventfd = -1; + if (ngx_ring.ring_fd != 0) { + io_uring_queue_exit(&ngx_ring); + ngx_ring.ring_fd = 0; } - ngx_aio_ctx = 0; - #endif ngx_free(event_list); @@ -939,84 +850,36 @@ ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags) #if (NGX_HAVE_FILE_AIO) static void -ngx_epoll_eventfd_handler(ngx_event_t *ev) +ngx_epoll_io_uring_handler(ngx_event_t *ev) { - int n, events; - long i; - uint64_t ready; - ngx_err_t err; ngx_event_t *e; + struct io_uring_cqe *cqe; + unsigned head; + unsigned cqe_count = 0; ngx_event_aio_t *aio; - struct io_event event[64]; - struct timespec ts; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "eventfd handler"); + ngx_log_debug(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "io_uring_peek_cqe: START"); - n = read(ngx_eventfd, &ready, 8); + io_uring_for_each_cqe(&ngx_ring, head, cqe) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "io_event: %p %d %d", + cqe->user_data, cqe->res, cqe->flags); - err = ngx_errno; + e = (ngx_event_t *) io_uring_cqe_get_data(cqe); + e->complete = 1; + e->active = 0; + e->ready = 1; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, "eventfd: %d", n); + aio = e->data; + aio->res = cqe->res; - if (n != 8) { - if (n == -1) { - if (err == NGX_EAGAIN) { - return; - } + ++cqe_count; - ngx_log_error(NGX_LOG_ALERT, ev->log, err, "read(eventfd) failed"); - return; - } - - ngx_log_error(NGX_LOG_ALERT, ev->log, 0, - "read(eventfd) returned only %d bytes", n); - return; + ngx_post_event(e, &ngx_posted_events); } - ts.tv_sec = 0; - ts.tv_nsec = 0; - - while (ready) { - - events = io_getevents(ngx_aio_ctx, 1, 64, event, &ts); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, - "io_getevents: %d", events); - - if (events > 0) { - ready -= events; - - for (i = 0; i < events; i++) { - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, ev->log, 0, - "io_event: %XL %XL %L %L", - event[i].data, event[i].obj, - event[i].res, event[i].res2); - - e = (ngx_event_t *) (uintptr_t) event[i].data; - - e->complete = 1; - e->active = 0; - e->ready = 1; - - aio = e->data; - aio->res = event[i].res; - - ngx_post_event(e, &ngx_posted_events); - } - - continue; - } - - if (events == 0) { - return; - } - - /* events == -1 */ - ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, - "io_getevents() failed"); - return; - } + io_uring_cq_advance(&ngx_ring, cqe_count); } #endif diff --git a/src/event/ngx_event.h b/src/event/ngx_event.h index 97f9673..eb17346 100644 --- a/src/event/ngx_event.h +++ b/src/event/ngx_event.h @@ -160,7 +160,9 @@ struct ngx_event_aio_s { size_t nbytes; #endif - ngx_aiocb_t aiocb; + /* Make sure that this iov has the same lifecycle with its associated aio event */ + struct iovec iov; + ngx_event_t event; }; diff --git a/src/os/unix/ngx_linux_aio_read.c b/src/os/unix/ngx_linux_aio_read.c index 9f0a6c1..033bd56 100644 --- a/src/os/unix/ngx_linux_aio_read.c +++ b/src/os/unix/ngx_linux_aio_read.c @@ -9,19 +9,15 @@ #include #include +#include -extern int ngx_eventfd; -extern aio_context_t ngx_aio_ctx; +extern struct io_uring ngx_ring; +extern struct io_uring_params ngx_ring_params; -static void ngx_file_aio_event_handler(ngx_event_t *ev); +static void ngx_file_aio_event_handler(ngx_event_t *ev); -static int -io_submit(aio_context_t ctx, long n, struct iocb **paiocb) -{ - return syscall(SYS_io_submit, ctx, n, paiocb); -} ngx_int_t @@ -50,10 +46,10 @@ ssize_t ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset, ngx_pool_t *pool) { - ngx_err_t err; - struct iocb *piocb[1]; - ngx_event_t *ev; - ngx_event_aio_t *aio; + ngx_err_t err; + ngx_event_t *ev; + ngx_event_aio_t *aio; + struct io_uring_sqe *sqe; if (!ngx_file_aio) { return ngx_read_file(file, buf, size, offset); @@ -93,22 +89,41 @@ ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset, return NGX_ERROR; } - ngx_memzero(&aio->aiocb, sizeof(struct iocb)); + sqe = io_uring_get_sqe(&ngx_ring); - aio->aiocb.aio_data = (uint64_t) (uintptr_t) ev; - aio->aiocb.aio_lio_opcode = IOCB_CMD_PREAD; - aio->aiocb.aio_fildes = file->fd; - aio->aiocb.aio_buf = (uint64_t) (uintptr_t) buf; - aio->aiocb.aio_nbytes = size; - aio->aiocb.aio_offset = offset; - aio->aiocb.aio_flags = IOCB_FLAG_RESFD; - aio->aiocb.aio_resfd = ngx_eventfd; + if (!sqe) { + ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0, + "aio no sqe left:%d @%O:%uz %V", + ev->complete, offset, size, &file->name); + return ngx_read_file(file, buf, size, offset); + } - ev->handler = ngx_file_aio_event_handler; + if (__builtin_expect(!!(ngx_ring_params.features & IORING_FEAT_CUR_PERSONALITY), 1)) { + /* + * `io_uring_prep_read` is faster than `io_uring_prep_readv`, because the kernel + * doesn't need to import iovecs in advance. + * + * If the kernel supports `IORING_FEAT_CUR_PERSONALITY`, it should support + * non-vectored read/write commands too. + * + * It's not perfect, but avoids an extra feature-test syscall. + */ + io_uring_prep_read(sqe, file->fd, buf, size, offset); + } else { + /* + * We must store iov into heap to prevent kernel from returning -EFAULT + * in case `IORING_FEAT_SUBMIT_STABLE` is not supported + */ + aio->iov.iov_base = buf; + aio->iov.iov_len = size; + io_uring_prep_readv(sqe, file->fd, &aio->iov, 1, offset); + } + io_uring_sqe_set_data(sqe, ev); - piocb[0] = &aio->aiocb; - if (io_submit(ngx_aio_ctx, 1, piocb) == 1) { + ev->handler = ngx_file_aio_event_handler; + + if (io_uring_submit(&ngx_ring) == 1) { ev->active = 1; ev->ready = 0; ev->complete = 0; diff --git a/src/os/unix/ngx_linux_config.h b/src/os/unix/ngx_linux_config.h index 3036cae..0e461dd 100644 --- a/src/os/unix/ngx_linux_config.h +++ b/src/os/unix/ngx_linux_config.h @@ -93,10 +93,6 @@ extern ssize_t sendfile(int s, int fd, int32_t *offset, size_t size); #include #endif #include -#if (NGX_HAVE_FILE_AIO) -#include -typedef struct iocb ngx_aiocb_t; -#endif #if (NGX_HAVE_CAPABILITIES) -- 2.25.1 ================================================ FILE: nginx_with_quic.patch ================================================ Add HTTP3(QUIC) Support. Add HTTP2 HPACK Encoding Support. Add Dynamic TLS Record Support. Using: patch -p1 < nginx_with_quic.patch Based on cloudflare/quiche c9311a1. Requires nginx 1.21.4 or later to patch. diff --color -uNr a/auto/lib/conf b/auto/lib/conf --- a/auto/lib/conf 2022-12-13 23:53:53.000000000 +0800 +++ b/auto/lib/conf 2023-03-12 11:07:31.415411871 +0800 @@ -25,6 +25,10 @@ . auto/lib/openssl/conf fi +if [ $USE_QUICHE = YES ]; then + . auto/lib/quiche/conf +fi + if [ $USE_ZLIB = YES ]; then . auto/lib/zlib/conf fi diff --color -uNr a/auto/lib/make b/auto/lib/make --- a/auto/lib/make 2022-12-13 23:53:53.000000000 +0800 +++ b/auto/lib/make 2023-03-12 11:07:31.415411871 +0800 @@ -11,6 +11,10 @@ . auto/lib/openssl/make fi +if [ $QUICHE != NONE -a $QUICHE != NO -a $QUICHE != YES ]; then + . auto/lib/quiche/make +fi + if [ $ZLIB != NONE -a $ZLIB != NO -a $ZLIB != YES ]; then . auto/lib/zlib/make fi diff --color -uNr a/auto/lib/openssl/make b/auto/lib/openssl/make --- a/auto/lib/openssl/make 2022-12-13 23:53:53.000000000 +0800 +++ b/auto/lib/openssl/make 2023-03-12 11:07:31.415411871 +0800 @@ -49,11 +49,13 @@ cat << END >> $NGX_MAKEFILE $OPENSSL/.openssl/include/openssl/ssl.h: $NGX_MAKEFILE - cd $OPENSSL \\ - && if [ -f Makefile ]; then \$(MAKE) clean; fi \\ - && ./config --prefix=$ngx_prefix no-shared no-threads $OPENSSL_OPT \\ - && \$(MAKE) \\ - && \$(MAKE) install_sw LIBDIR=lib + mkdir -p $OPENSSL/build $OPENSSL/.openssl/lib $OPENSSL/.openssl/include/openssl \\ + && cd $OPENSSL/build \\ + && cmake -DCMAKE_C_FLAGS="$OPENSSL_OPT" -DCMAKE_CXX_FLAGS="$OPENSSL_OPT" .. \\ + && \$(MAKE) VERBOSE=1 \\ + && cd .. \\ + && cp -r src/include/openssl/*.h .openssl/include/openssl \\ + && cp build/libssl.a build/libcrypto.a .openssl/lib END diff --color -uNr a/auto/lib/quiche/conf b/auto/lib/quiche/conf --- a/auto/lib/quiche/conf 1970-01-01 08:00:00.000000000 +0800 +++ b/auto/lib/quiche/conf 2023-03-12 11:07:31.415411871 +0800 @@ -0,0 +1,23 @@ + +# Copyright (C) Cloudflare, Inc. + + +if [ $QUICHE != NONE ]; then + + have=NGX_QUIC . auto/have + + QUICHE_BUILD_TARGET="release" + + if [ $NGX_DEBUG = YES ]; then + QUICHE_BUILD_TARGET="debug" + fi + + CORE_INCS="$CORE_INCS $QUICHE/quiche/include" + CORE_DEPS="$CORE_DEPS $QUICHE/target/$QUICHE_BUILD_TARGET/libquiche.a" + CORE_LIBS="$CORE_LIBS $QUICHE/target/$QUICHE_BUILD_TARGET/libquiche.a $NGX_LIBPTHREAD -lm" + + if [ "$NGX_SYSTEM" = "Darwin" ]; then + CORE_LIBS+=" -framework Security" + fi + +fi diff --color -uNr a/auto/lib/quiche/make b/auto/lib/quiche/make --- a/auto/lib/quiche/make 1970-01-01 08:00:00.000000000 +0800 +++ b/auto/lib/quiche/make 2023-03-12 11:07:31.415411871 +0800 @@ -0,0 +1,23 @@ + +# Copyright (C) Cloudflare, Inc. + +QUICHE_COMMON_FLAGS="--package quiche --verbose --no-default-features --features ffi" + +# Default is release build +QUICHE_BUILD_FLAGS="$QUICHE_COMMON_FLAGS --release" +QUICHE_BUILD_TARGET="release" + +if [ $NGX_DEBUG = YES ]; then + QUICHE_BUILD_FLAGS="$QUICHE_COMMON_FLAGS" + QUICHE_BUILD_TARGET="debug" +fi + + +cat << END >> $NGX_MAKEFILE + +$QUICHE/target/$QUICHE_BUILD_TARGET/libquiche.a: \\ + $OPENSSL/.openssl/include/openssl/ssl.h \\ + $NGX_MAKEFILE + cd $QUICHE && cargo build $QUICHE_BUILD_FLAGS $QUICHE_OPT + +END diff --color -uNr a/auto/make b/auto/make --- a/auto/make 2022-12-13 23:53:53.000000000 +0800 +++ b/auto/make 2023-03-12 11:07:31.415411871 +0800 @@ -7,7 +7,8 @@ mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \ $NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \ - $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \ + $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \ + $NGX_OBJS/src/http/modules \ $NGX_OBJS/src/http/modules/perl \ $NGX_OBJS/src/mail \ $NGX_OBJS/src/stream \ diff --color -uNr a/auto/modules b/auto/modules --- a/auto/modules 2022-12-13 23:53:53.000000000 +0800 +++ b/auto/modules 2023-03-12 11:07:39.510625293 +0800 @@ -124,6 +124,7 @@ # ngx_http_header_filter # ngx_http_chunked_filter # ngx_http_v2_filter + # ngx_http_v3_filter # ngx_http_range_header_filter # ngx_http_gzip_filter # ngx_http_postpone_filter @@ -156,6 +157,7 @@ ngx_http_header_filter_module \ ngx_http_chunked_filter_module \ ngx_http_v2_filter_module \ + ngx_http_v3_filter_module \ ngx_http_range_header_filter_module \ ngx_http_gzip_filter_module \ ngx_http_postpone_filter_module \ @@ -217,6 +219,17 @@ . auto/module fi + if [ $HTTP_V3 = YES ]; then + ngx_module_name=ngx_http_v3_filter_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs=src/http/v3/ngx_http_v3_filter_module.c + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + if :; then ngx_module_name=ngx_http_range_header_filter_module ngx_module_incs= @@ -426,6 +439,28 @@ . auto/module fi + if [ $HTTP_V3 = YES ]; then + USE_QUICHE=YES + USE_OPENSSL=YES + have=NGX_HTTP_V3 . auto/have + have=NGX_HTTP_HEADERS . auto/have + + ngx_module_name=ngx_http_v3_module + ngx_module_incs=src/http/v3 + ngx_module_deps="src/http/v3/ngx_http_v3.h \ + src/http/v3/ngx_http_v3_module.h" + ngx_module_srcs="src/http/v3/ngx_http_v3.c \ + src/http/v3/ngx_http_v3_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + + if [ $HTTP_V2_HPACK_ENC = YES ]; then + have=NGX_HTTP_V2_HPACK_ENC . auto/have + fi + if :; then ngx_module_name=ngx_http_static_module ngx_module_incs= @@ -1270,6 +1305,19 @@ . auto/module fi + + +if [ $USE_QUICHE = YES ]; then + ngx_module_type=CORE + ngx_module_name=ngx_quic_module + ngx_module_incs= + ngx_module_deps=src/event/ngx_event_quic.h + ngx_module_srcs=src/event/ngx_event_quic.c + ngx_module_libs= + ngx_module_link=YES + + . auto/module +fi if [ $USE_PCRE = YES ]; then diff --color -uNr a/auto/options b/auto/options --- a/auto/options 2022-12-13 23:53:53.000000000 +0800 +++ b/auto/options 2023-03-12 11:07:39.511625319 +0800 @@ -59,6 +59,8 @@ HTTP_GZIP=YES HTTP_SSL=NO HTTP_V2=NO +HTTP_V2_HPACK_ENC=NO +HTTP_V3=NO HTTP_SSI=YES HTTP_REALIP=NO HTTP_XSLT=NO @@ -151,6 +153,9 @@ USE_OPENSSL=NO OPENSSL=NONE +USE_QUICHE=NO +QUICHE=NONE + USE_ZLIB=NO ZLIB=NONE ZLIB_OPT= @@ -228,6 +233,8 @@ --with-http_ssl_module) HTTP_SSL=YES ;; --with-http_v2_module) HTTP_V2=YES ;; + --with-http_v2_hpack_enc) HTTP_V2_HPACK_ENC=YES ;; + --with-http_v3_module) HTTP_V3=YES ;; --with-http_realip_module) HTTP_REALIP=YES ;; --with-http_addition_module) HTTP_ADDITION=YES ;; --with-http_xslt_module) HTTP_XSLT=YES ;; @@ -363,6 +370,9 @@ --with-openssl=*) OPENSSL="$value" ;; --with-openssl-opt=*) OPENSSL_OPT="$value" ;; + --with-quiche=*) QUICHE="$value" ;; + --with-quiche-opt=*) QUICHE_OPT="$value" ;; + --with-md5=*) NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG $0: warning: the \"--with-md5\" option is deprecated" @@ -445,6 +455,8 @@ --with-http_ssl_module enable ngx_http_ssl_module --with-http_v2_module enable ngx_http_v2_module + --with-http_v2_hpack_enc enable ngx_http_v2_hpack_enc + --with-http_v3_module enable ngx_http_v3_module --with-http_realip_module enable ngx_http_realip_module --with-http_addition_module enable ngx_http_addition_module --with-http_xslt_module enable ngx_http_xslt_module diff --color -uNr a/src/core/ngx_connection.h b/src/core/ngx_connection.h --- a/src/core/ngx_connection.h 2022-12-13 23:53:53.000000000 +0800 +++ b/src/core/ngx_connection.h 2023-03-12 11:07:31.416411897 +0800 @@ -77,6 +77,9 @@ unsigned deferred_accept:1; unsigned delete_deferred:1; unsigned add_deferred:1; +#if (NGX_QUIC) + unsigned quic:1; +#endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) char *accept_filter; #endif @@ -153,6 +156,10 @@ ngx_udp_connection_t *udp; +#if (NGX_QUIC) + ngx_quic_connection_t *quic; +#endif + struct sockaddr *local_sockaddr; socklen_t local_socklen; diff --color -uNr a/src/core/ngx_core.h b/src/core/ngx_core.h --- a/src/core/ngx_core.h 2022-12-13 23:53:53.000000000 +0800 +++ b/src/core/ngx_core.h 2023-03-12 11:07:31.416411897 +0800 @@ -83,6 +83,9 @@ #if (NGX_OPENSSL) #include #endif +#if (NGX_QUIC) +#include +#endif #include #include #include diff --color -uNr a/src/core/ngx_murmurhash.c b/src/core/ngx_murmurhash.c --- a/src/core/ngx_murmurhash.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/core/ngx_murmurhash.c 2023-03-12 11:07:39.511625319 +0800 @@ -50,3 +50,63 @@ return h; } + + +uint64_t +ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed) +{ + uint64_t h, k; + + h = seed ^ len; + + while (len >= 8) { + k = data[0]; + k |= data[1] << 8; + k |= data[2] << 16; + k |= data[3] << 24; + k |= (uint64_t)data[4] << 32; + k |= (uint64_t)data[5] << 40; + k |= (uint64_t)data[6] << 48; + k |= (uint64_t)data[7] << 56; + + k *= 0xc6a4a7935bd1e995ull; + k ^= k >> 47; + k *= 0xc6a4a7935bd1e995ull; + + h ^= k; + h *= 0xc6a4a7935bd1e995ull; + + data += 8; + len -= 8; + } + + switch (len) { + case 7: + h ^= (uint64_t)data[6] << 48; + /* fall through */ + case 6: + h ^= (uint64_t)data[5] << 40; + /* fall through */ + case 5: + h ^= (uint64_t)data[4] << 32; + /* fall through */ + case 4: + h ^= data[3] << 24; + /* fall through */ + case 3: + h ^= data[2] << 16; + /* fall through */ + case 2: + h ^= data[1] << 8; + /* fall through */ + case 1: + h ^= data[0]; + h *= 0xc6a4a7935bd1e995ull; + } + + h ^= h >> 47; + h *= 0xc6a4a7935bd1e995ull; + h ^= h >> 47; + + return h; +} diff --color -uNr a/src/core/ngx_murmurhash.h b/src/core/ngx_murmurhash.h --- a/src/core/ngx_murmurhash.h 2022-12-13 23:53:53.000000000 +0800 +++ b/src/core/ngx_murmurhash.h 2023-03-12 11:07:39.511625319 +0800 @@ -15,5 +15,7 @@ uint32_t ngx_murmur_hash2(u_char *data, size_t len); +uint64_t ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed); + #endif /* _NGX_MURMURHASH_H_INCLUDED_ */ diff --color -uNr a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/event/ngx_event_openssl.c 2023-03-12 11:07:39.512625346 +0800 @@ -1677,6 +1677,7 @@ sc->buffer = ((flags & NGX_SSL_BUFFER) != 0); sc->buffer_size = ssl->buffer_size; + sc->dyn_rec = ssl->dyn_rec; sc->session_ctx = ssl->ctx; @@ -2648,6 +2649,41 @@ for ( ;; ) { + /* Dynamic record resizing: + We want the initial records to fit into one TCP segment + so we don't get TCP HoL blocking due to TCP Slow Start. + A connection always starts with small records, but after + a given amount of records sent, we make the records larger + to reduce header overhead. + After a connection has idled for a given timeout, begin + the process from the start. The actual parameters are + configurable. If dyn_rec_timeout is 0, we assume dyn_rec is off. */ + + if (c->ssl->dyn_rec.timeout > 0 ) { + + if (ngx_current_msec - c->ssl->dyn_rec_last_write > + c->ssl->dyn_rec.timeout) + { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + c->ssl->dyn_rec_records_sent = 0; + + } else { + if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold * 2) + { + buf->end = buf->start + c->ssl->buffer_size; + + } else if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold) + { + buf->end = buf->start + c->ssl->dyn_rec.size_hi; + + } else { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + } + } + } + while (in && buf->last < buf->end && send < limit) { if (in->buf->last_buf || in->buf->flush) { flush = 1; @@ -2787,6 +2823,9 @@ if (n > 0) { + c->ssl->dyn_rec_records_sent++; + c->ssl->dyn_rec_last_write = ngx_current_msec; + if (c->ssl->saved_read_handler) { c->read->handler = c->ssl->saved_read_handler; diff --color -uNr a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h 2022-12-13 23:53:53.000000000 +0800 +++ b/src/event/ngx_event_openssl.h 2023-03-12 11:07:39.513625372 +0800 @@ -78,10 +78,19 @@ typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; +typedef struct { + ngx_msec_t timeout; + ngx_uint_t threshold; + size_t size_lo; + size_t size_hi; +} ngx_ssl_dyn_rec_t; + + struct ngx_ssl_s { SSL_CTX *ctx; ngx_log_t *log; size_t buffer_size; + ngx_ssl_dyn_rec_t dyn_rec; }; @@ -120,6 +129,10 @@ unsigned in_ocsp:1; unsigned early_preread:1; unsigned write_blocked:1; + + ngx_ssl_dyn_rec_t dyn_rec; + ngx_msec_t dyn_rec_last_write; + ngx_uint_t dyn_rec_records_sent; }; @@ -129,7 +142,7 @@ #define NGX_SSL_DFLT_BUILTIN_SCACHE -5 -#define NGX_SSL_MAX_SESSION_SIZE 4096 +#define NGX_SSL_MAX_SESSION_SIZE 16384 typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t; diff --color -uNr a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c --- a/src/event/ngx_event_quic.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/event/ngx_event_quic.c 2023-03-12 11:07:31.417411924 +0800 @@ -0,0 +1,625 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + +#include +#include +#include + + +/* Limit outgoing packets to 1200 bytes. This is the minimum value allowed. */ +#define MAX_DATAGRAM_SIZE 1200 + +/* errors */ +#define NGX_QUIC_NO_ERROR 0x0 +#define NGX_QUIC_INTERNAL_ERROR 0x1 + + +static void ngx_quic_read_handler(ngx_event_t *ev); +static void ngx_quic_write_handler(ngx_event_t *ev); + +static void ngx_quic_set_timer(ngx_connection_t *c); + +static void ngx_quic_handshake_completed(ngx_connection_t *c); + +static void ngx_quic_shutdown_handler(ngx_event_t *ev); + +static void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t status); +static void ngx_quic_close_connection(ngx_connection_t *c); + +static ngx_int_t ngx_quic_send_udp_packet(ngx_connection_t *c, uint8_t *buf, + size_t len); + + +static ngx_command_t ngx_quic_commands[] = { + + ngx_null_command +}; + + +static ngx_core_module_t ngx_quic_module_ctx = { + ngx_string("quic"), + NULL, + NULL +}; + + +ngx_module_t ngx_quic_module = { + NGX_MODULE_V1, + &ngx_quic_module_ctx, /* module context */ + ngx_quic_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +ngx_int_t +ngx_quic_create_conf(ngx_quic_t *quic) +{ + quic->config = quiche_config_new(QUICHE_PROTOCOL_VERSION); + if (quic->config == NULL) { + ngx_log_error(NGX_LOG_EMERG, quic->log, 0, "failed to create quic config"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_validate_initial(ngx_event_t *ev, u_char *buf, ssize_t buf_len) +{ + /* Check incoming packet type, if it's not Initial we shouldn't be here. */ + if (((buf[0] & 0x30) >> 4) != 0) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "packet is not quic client initial"); + return NGX_ERROR; + } + + /* Client Initial packets must be at least 1200 bytes. */ + if (buf_len < QUICHE_MIN_CLIENT_INITIAL_LEN) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "quic initial packet is too short"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_create_connection(ngx_quic_t *quic, ngx_connection_t *c) +{ + int rc; + u_char *buf; + size_t buf_len; + quiche_conn *conn; + static uint8_t out[MAX_DATAGRAM_SIZE]; + + uint8_t pkt_type; + uint32_t pkt_version; + + uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; + size_t scid_len = sizeof(scid); + + uint8_t dcid[QUICHE_MAX_CONN_ID_LEN]; + size_t dcid_len = sizeof(dcid); + + uint8_t token[1]; + size_t token_len = sizeof(token); + + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init connection"); + + /* Extract some fields from the client's Initial packet, which was saved + * into c->buffer by ngx_event_recvmsg(). */ + buf = c->buffer->pos; + buf_len = ngx_buf_size(c->buffer); + + rc = quiche_header_info(buf, buf_len, QUICHE_MAX_CONN_ID_LEN, + &pkt_version, &pkt_type, + scid, &scid_len, dcid, &dcid_len, + token, &token_len); + if (rc < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to parse quic header: %d", rc); + return NGX_ERROR; + } + + /* Version mismatch, do version negotiation. */ + if (!quiche_version_is_supported(pkt_version)) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic version negotiation"); + + ssize_t written = quiche_negotiate_version(scid, scid_len, + dcid, dcid_len, + out, sizeof(out)); + + if (written < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to create quic vneg packet: %d", written); + return NGX_ERROR; + } + + if (ngx_quic_send_udp_packet(c, out, written) == NGX_ERROR) { + return NGX_ERROR; + } + + return NGX_DONE; + } + + /* Initialize source connection ID with some random bytes. */ + RAND_bytes(scid, sizeof(scid)); + +#if (NGX_DEBUG) + { + uint8_t dcid_hex[QUICHE_MAX_CONN_ID_LEN * 2], + scid_hex[QUICHE_MAX_CONN_ID_LEN * 2]; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "new quic connection dcid:%*.s new_scid:%*.s", + ngx_hex_dump(dcid_hex, dcid, dcid_len) - dcid_hex, dcid_hex, + ngx_hex_dump(scid_hex, scid, sizeof(scid)) - scid_hex, scid_hex); + } +#endif + + conn = quiche_conn_new_with_tls(scid, sizeof(scid), NULL, 0, + c->local_sockaddr, c->local_socklen, + c->sockaddr, c->socklen, quic->config, + c->ssl->connection, true); + if (conn == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create quic connection"); + return NGX_ERROR; + } + + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + quiche_conn_free(conn); + return NGX_ERROR; + } + + qc->handler = NULL; + + qc->conn = conn; + + c->quic = qc; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handshake(ngx_connection_t *c) +{ + u_char *buf; + size_t buf_len; + ssize_t done; + + quiche_recv_info recv_info = { + c->sockaddr, + c->socklen, + c->local_sockaddr, + c->local_socklen, + }; + + /* Process the client's Initial packet, which was saved into c->buffer by + * ngx_event_recvmsg(). */ + buf = c->buffer->pos; + buf_len = ngx_buf_size(c->buffer); + + done = quiche_conn_recv(c->quic->conn, buf, buf_len, &recv_info); + + if ((done < 0) && (done != QUICHE_ERR_DONE)) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to process quic packet: %d", done); + return NGX_ERROR; + } + + c->read->handler = ngx_quic_read_handler; + c->write->handler = ngx_quic_write_handler; + + ngx_post_event(c->write, &ngx_posted_events); + + return NGX_AGAIN; +} + + +static void +ngx_quic_read_handler(ngx_event_t *rev) +{ + int n; + static uint8_t buf[65535]; + ngx_connection_t *c; + + c = rev->data; + + c->log->action = "reading QUIC packets"; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic read handler"); + + if (rev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection timed out"); + + if (c->quic->handler != NULL) { + c->quic->handler(c); + } + + return; + } + + for (;;) { + n = c->recv(c, buf, sizeof(buf)); + if (n == NGX_AGAIN) { + break; + } + + if (n == NGX_ERROR) { + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + + quiche_recv_info recv_info = { + c->sockaddr, + c->socklen, + c->local_sockaddr, + c->local_socklen, + }; + + ssize_t done = quiche_conn_recv(c->quic->conn, buf, n, &recv_info); + + if (done == QUICHE_ERR_DONE) { + break; + } + + if (done < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to process quic packet: %d", done); + + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + } + + if (quiche_conn_is_in_early_data(c->quic->conn) || + quiche_conn_is_established(c->quic->conn)) { + if (!c->ssl->handshaked) { + ngx_quic_handshake_completed(c); + } + + if ((c->quic == NULL) || (c->quic->handler == NULL)) { + return; + } + + /* Notify application layer that there might be stream data to read. */ + c->quic->handler(c); + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic done reading"); + + ngx_post_event(c->write, &ngx_posted_events); +} + + +static void +ngx_quic_write_handler(ngx_event_t *wev) +{ + ngx_connection_t *c; + quiche_send_info send_info; + static uint8_t out[MAX_DATAGRAM_SIZE]; + + c = wev->data; + + c->log->action = "writing QUIC packets"; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic write handler"); + + if (wev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alarm fired"); + + quiche_conn_on_timeout(c->quic->conn); + } + + if (quiche_conn_is_closed(c->quic->conn)) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection is closed"); + + ngx_quic_finalize_connection(c, NGX_QUIC_NO_ERROR); + return; + } + + for (;;) { + ssize_t written = quiche_conn_send(c->quic->conn, out, sizeof(out), + &send_info); + + if (written == QUICHE_ERR_DONE) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic done writing"); + break; + } + + if (written < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to create quic packet: %d", written); + + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + + int rc = ngx_quic_send_udp_packet(c, out, written); + + if (rc == NGX_AGAIN) { + break; + } + + if (rc == NGX_ERROR) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to send quic packet"); + + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + } + + ngx_quic_set_timer(c); +} + + +static void +ngx_quic_set_timer(ngx_connection_t *c) +{ + uint64_t expiry; + ngx_event_t *wev; + + wev = c->write; + + expiry = quiche_conn_timeout_as_millis(c->quic->conn); + expiry = ngx_max(expiry, 1); + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + /* quiche_conn_timeout_as_millis() will return UINT64_MAX when the timer + * should be unset (this would be equvalent to returning Option::None in + * Rust). To avoid overflow we need to explicitly check for this value. */ + if (expiry != UINT64_MAX) { + ngx_add_timer(wev, (ngx_msec_t)expiry); + } +} + + +static void +ngx_quic_handshake_completed(ngx_connection_t *c) +{ +#if (NGX_DEBUG) + { + char buf[129], *s, *d; +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + const +#endif + SSL_CIPHER *cipher; + + cipher = SSL_get_current_cipher(c->ssl->connection); + + if (cipher) { + SSL_CIPHER_description(cipher, &buf[1], 128); + + for (s = &buf[1], d = buf; *s; s++) { + if (*s == ' ' && *d == ' ') { + continue; + } + + if (*s == LF || *s == CR) { + continue; + } + + *++d = *s; + } + + if (*d != ' ') { + d++; + } + + *d = '\0'; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "QUIC: %s, cipher: \"%s\"", + SSL_get_version(c->ssl->connection), &buf[1]); + + if (SSL_session_reused(c->ssl->connection)) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic reused session"); + } + + } else { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic no shared ciphers"); + } + } +#endif + + ngx_del_timer(c->read); + + c->ssl->handshaked = 1; + + /* Notify application layer that the handshake is complete. */ + c->ssl->handler(c); +} + + +ngx_int_t +ngx_quic_shutdown(ngx_connection_t *c) +{ + ssize_t written; + quiche_send_info send_info; + static uint8_t out[MAX_DATAGRAM_SIZE]; + + /* Connection is closed, free memory. */ + if (quiche_conn_is_closed(c->quic->conn)) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "free quic connection"); + + quiche_conn_free(c->quic->conn); + + c->quic = NULL; + c->ssl = NULL; + + return NGX_OK; + } + + /* We can't free the connection state yet, as we need to wait for the + * draining timeout to expire. + * + * Setup event handlers such that we will try again when that happens (or + * when another event is triggered). */ + c->read->handler = ngx_quic_shutdown_handler; + c->write->handler = ngx_quic_shutdown_handler; + + /* Try sending a packet in order to flush pending frames (CONNECTION_CLOSE + * for example), but ignore errors as we are already closing the connection + * anyway. */ + written = quiche_conn_send(c->quic->conn, out, sizeof(out), &send_info); + + if (written > 0) { + ngx_quic_send_udp_packet(c, out, written); + } + + ngx_quic_set_timer(c); + + return NGX_AGAIN; +} + + +static void +ngx_quic_shutdown_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_connection_handler_pt handler; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic shutdown handler"); + + c = ev->data; + handler = c->quic->handler; + + if (ev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alarm fired"); + + quiche_conn_on_timeout(c->quic->conn); + } + + if (ngx_quic_shutdown(c) == NGX_AGAIN) { + return; + } + + handler(c); +} + + +static void +ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t status) +{ + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "finalize quic connection: %d", c->fd); + + c->error = 1; + + if (quiche_conn_is_closed(c->quic->conn)) { + c->close = 1; + } + + quiche_conn_close(c->quic->conn, false, status, NULL, 0); + + /* Notify the application layer that the connection is in an error + * state and will be closed. */ + if (c->quic->handler != NULL) { + c->quic->handler(c); + return; + } + + ngx_quic_close_connection(c); +} + + +static void +ngx_quic_close_connection(ngx_connection_t *c) +{ + ngx_pool_t *pool; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "close quic connection: %d", c->fd); + + if (c->quic) { + if (ngx_quic_shutdown(c) == NGX_AGAIN) { + c->quic->handler = ngx_quic_close_connection; + return; + } + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} + + +void +ngx_quic_cleanup_ctx(void *data) +{ + ngx_quic_t *quic = data; + + quiche_config_free(quic->config); +} + + +static ngx_int_t +ngx_quic_send_udp_packet(ngx_connection_t *c, uint8_t *buf, size_t len) +{ + ngx_buf_t out_buf = {0}; + ngx_chain_t out_chain = {0}; + ngx_chain_t *cl; + + /* The send_chain() API takes an ngx_chain_t parameter instead of a simple + * buffer, so we need to initialize the chain such that it contains only a + * single buffer. + * + * The c->send_chain() call is required (instead of just c->send()) because + * it uses the sendmsg(2) syscall (instead of sendto(2)), which allows us to + * specify the correct source IP address for the connection. */ + + out_buf.start = out_buf.pos = buf; + out_buf.end = out_buf.last = buf + len; + out_buf.memory = 1; + out_buf.flush = 1; + + out_chain.buf = &out_buf; + out_chain.next = NULL; + + c->write->ready = 1; + + cl = c->send_chain(c, &out_chain, 0); + + if (cl != NULL) { + return NGX_AGAIN; + } + + if (cl == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + return NGX_OK; +} diff --color -uNr a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h --- a/src/event/ngx_event_quic.h 1970-01-01 08:00:00.000000000 +0800 +++ b/src/event/ngx_event_quic.h 2023-03-12 11:07:31.417411924 +0800 @@ -0,0 +1,49 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_H_INCLUDED_ +#define _NGX_EVENT_QUIC_H_INCLUDED_ + + +#include + +#include +#include + +#include + +typedef struct ngx_quic_s ngx_quic_t; +typedef struct ngx_quic_connection_s ngx_quic_connection_t; + +struct ngx_quic_s { + quiche_config *config; + ngx_log_t *log; +}; + +struct ngx_quic_connection_s { + quiche_conn *conn; + + ngx_connection_handler_pt handler; +}; + + +ngx_int_t ngx_quic_create_conf(ngx_quic_t *quic); + +ngx_int_t ngx_quic_validate_initial(ngx_event_t *ev, u_char *buf, + ssize_t buf_len); + +ngx_int_t ngx_quic_create_connection(ngx_quic_t *quic, ngx_connection_t *c); + +ngx_int_t ngx_quic_create_ssl_connection(ngx_ssl_t *ssl, ngx_connection_t *c, + ngx_uint_t flags); + +ngx_int_t ngx_quic_handshake(ngx_connection_t *c); + +ngx_int_t ngx_quic_shutdown(ngx_connection_t *c); + +void ngx_quic_cleanup_ctx(void *data); + +#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --color -uNr a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c --- a/src/event/ngx_event_udp.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/event/ngx_event_udp.c 2023-03-12 11:07:31.417411924 +0800 @@ -204,6 +204,14 @@ (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1); #endif +#if (NGX_QUIC) + if (ls->quic) { + if (ngx_quic_validate_initial(ev, buffer, n) != NGX_OK) { + goto next; + } + } +#endif + ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n; diff --color -uNr a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.c 2023-03-12 11:07:39.513625372 +0800 @@ -296,6 +296,41 @@ offsetof(ngx_http_ssl_srv_conf_t, reject_handshake), NULL }, + { ngx_string("ssl_dyn_rec_enable"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_enable), + NULL }, + + { ngx_string("ssl_dyn_rec_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_timeout), + NULL }, + + { ngx_string("ssl_dyn_rec_size_lo"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_lo), + NULL }, + + { ngx_string("ssl_dyn_rec_size_hi"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_hi), + NULL }, + + { ngx_string("ssl_dyn_rec_threshold"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_threshold), + NULL }, + ngx_null_command }; @@ -424,7 +459,7 @@ #if (NGX_DEBUG) unsigned int i; #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) ngx_http_connection_t *hc; #endif #if (NGX_HTTP_V2 || NGX_DEBUG) @@ -441,14 +476,23 @@ } #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) hc = c->data; +#endif +#if (NGX_HTTP_V2) if (hc->addr_conf->http2) { srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS; srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1; } else #endif +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + srv = (unsigned char *) QUICHE_H3_APPLICATION_PROTOCOL; + srvlen = sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1; + + } else +#endif { srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS; srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1; @@ -598,6 +642,11 @@ sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; sscf->stapling = NGX_CONF_UNSET; sscf->stapling_verify = NGX_CONF_UNSET; + sscf->dyn_rec_enable = NGX_CONF_UNSET; + sscf->dyn_rec_timeout = NGX_CONF_UNSET_MSEC; + sscf->dyn_rec_size_lo = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_size_hi = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_threshold = NGX_CONF_UNSET_UINT; return sscf; } @@ -673,6 +722,20 @@ ngx_conf_merge_str_value(conf->stapling_responder, prev->stapling_responder, ""); + ngx_conf_merge_value(conf->dyn_rec_enable, prev->dyn_rec_enable, 0); + ngx_conf_merge_msec_value(conf->dyn_rec_timeout, prev->dyn_rec_timeout, + 1000); + /* Default sizes for the dynamic record sizes are defined to fit maximal + TLS + IPv6 overhead in a single TCP segment for lo and 3 segments for hi: + 1369 = 1500 - 40 (IP) - 20 (TCP) - 10 (Time) - 61 (Max TLS overhead) */ + ngx_conf_merge_size_value(conf->dyn_rec_size_lo, prev->dyn_rec_size_lo, + 1369); + /* 4229 = (1500 - 40 - 20 - 10) * 3 - 61 */ + ngx_conf_merge_size_value(conf->dyn_rec_size_hi, prev->dyn_rec_size_hi, + 4229); + ngx_conf_merge_uint_value(conf->dyn_rec_threshold, prev->dyn_rec_threshold, + 40); + conf->ssl.log = cf->log; if (conf->enable) { @@ -899,6 +962,28 @@ return NGX_CONF_ERROR; } + if (conf->dyn_rec_enable) { + conf->ssl.dyn_rec.timeout = conf->dyn_rec_timeout; + conf->ssl.dyn_rec.threshold = conf->dyn_rec_threshold; + + if (conf->buffer_size > conf->dyn_rec_size_lo) { + conf->ssl.dyn_rec.size_lo = conf->dyn_rec_size_lo; + + } else { + conf->ssl.dyn_rec.size_lo = conf->buffer_size; + } + + if (conf->buffer_size > conf->dyn_rec_size_hi) { + conf->ssl.dyn_rec.size_hi = conf->dyn_rec_size_hi; + + } else { + conf->ssl.dyn_rec.size_hi = conf->buffer_size; + } + + } else { + conf->ssl.dyn_rec.timeout = 0; + } + return NGX_CONF_OK; } diff --color -uNr a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.h 2023-03-12 11:07:39.514625398 +0800 @@ -67,6 +67,12 @@ u_char *file; ngx_uint_t line; + + ngx_flag_t dyn_rec_enable; + ngx_msec_t dyn_rec_timeout; + size_t dyn_rec_size_lo; + size_t dyn_rec_size_hi; + ngx_uint_t dyn_rec_threshold; } ngx_http_ssl_srv_conf_t; diff --color -uNr a/src/http/ngx_http.c b/src/http/ngx_http.c --- a/src/http/ngx_http.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/ngx_http.c 2023-03-12 11:07:31.418411950 +0800 @@ -1178,6 +1178,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, ngx_http_listen_opt_t *lsopt) { + int t; in_port_t p; ngx_uint_t i; struct sockaddr *sa; @@ -1196,11 +1197,13 @@ sa = lsopt->sockaddr; p = ngx_inet_get_port(sa); + t = lsopt->quic ? SOCK_DGRAM : SOCK_STREAM; port = cmcf->ports->elts; for (i = 0; i < cmcf->ports->nelts; i++) { - if (p != port[i].port || sa->sa_family != port[i].family) { + if (p != port[i].port || sa->sa_family != port[i].family + || t != port[i].type) { continue; } @@ -1219,6 +1222,7 @@ port->family = sa->sa_family; port->port = p; port->addrs.elts = NULL; + port->type = t; return ngx_http_add_address(cf, cscf, port, lsopt); } @@ -1236,6 +1240,9 @@ #if (NGX_HTTP_V2) ngx_uint_t http2; #endif +#if (NGX_HTTP_V3) + ngx_uint_t quic; +#endif /* * we cannot compare whole sockaddr struct's as kernel @@ -1271,6 +1278,9 @@ #if (NGX_HTTP_V2) http2 = lsopt->http2 || addr[i].opt.http2; #endif +#if (NGX_HTTP_V3) + quic = lsopt->quic || addr[i].opt.quic; +#endif if (lsopt->set) { @@ -1307,6 +1317,9 @@ #if (NGX_HTTP_V2) addr[i].opt.http2 = http2; #endif +#if (NGX_HTTP_V3) + addr[i].opt.quic = quic; +#endif return NGX_OK; } @@ -1724,6 +1737,12 @@ break; } +#if (NGX_HTTP_V3) + if (addr[i].opt.quic) { + ls->type = SOCK_DGRAM; + } +#endif + addr++; last--; } @@ -1805,6 +1824,12 @@ ls->reuseport = addr->opt.reuseport; #endif +#if (NGX_HTTP_V3) + ls->quic = addr->opt.quic; + + ls->wildcard = addr->opt.wildcard; +#endif + return ls; } @@ -1838,6 +1863,9 @@ addrs[i].conf.http2 = addr[i].opt.http2; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; +#if (NGX_HTTP_V3) + addrs[i].conf.quic = addr[i].opt.quic; +#endif if (addr[i].hash.buckets == NULL && (addr[i].wc_head == NULL @@ -1903,6 +1931,9 @@ addrs6[i].conf.http2 = addr[i].opt.http2; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; +#if (NGX_HTTP_V3) + addrs6[i].conf.quic = addr[i].opt.quic; +#endif if (addr[i].hash.buckets == NULL && (addr[i].wc_head == NULL diff --color -uNr a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/ngx_http_core_module.c 2023-03-12 11:07:31.419411976 +0800 @@ -4283,6 +4283,13 @@ continue; } +#if (NGX_HTTP_V3) + if (ngx_strcmp(value[n].data, "quic") == 0) { + lsopt.quic = 1; + continue; + } +#endif + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[n]); return NGX_CONF_ERROR; diff --color -uNr a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h --- a/src/http/ngx_http_core_module.h 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/ngx_http_core_module.h 2023-03-12 11:07:31.419411976 +0800 @@ -82,6 +82,7 @@ unsigned reuseport:1; unsigned so_keepalive:2; unsigned proxy_protocol:1; + unsigned quic:1; int backlog; int rcvbuf; @@ -238,6 +239,7 @@ unsigned ssl:1; unsigned http2:1; unsigned proxy_protocol:1; + unsigned quic:1; }; @@ -268,6 +270,7 @@ ngx_int_t family; in_port_t port; ngx_array_t addrs; /* array of ngx_http_conf_addr_t */ + ngx_int_t type; } ngx_http_conf_port_t; diff --color -uNr a/src/http/ngx_http.h b/src/http/ngx_http.h --- a/src/http/ngx_http.h 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/ngx_http.h 2023-03-12 11:07:31.418411950 +0800 @@ -20,6 +20,7 @@ typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t; typedef struct ngx_http_chunked_s ngx_http_chunked_t; typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t; +typedef struct ngx_http_v3_stream_s ngx_http_v3_stream_t; typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); @@ -38,6 +39,9 @@ #if (NGX_HTTP_V2) #include #endif +#if (NGX_HTTP_V3) +#include +#endif #if (NGX_HTTP_CACHE) #include #endif diff --color -uNr a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c --- a/src/http/ngx_http_request_body.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/ngx_http_request_body.c 2023-03-12 11:07:31.421412029 +0800 @@ -314,6 +314,12 @@ ngx_del_timer(c->read); } +#if (NGX_HTTP_V3) + if (r->qstream) { + return NGX_AGAIN; + } +#endif + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -418,6 +424,12 @@ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ngx_add_timer(c->read, clcf->client_body_timeout); +#if (NGX_HTTP_V3) + if (r->qstream) { + return NGX_AGAIN; + } +#endif + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -625,6 +637,17 @@ } #endif +#if (NGX_HTTP_V3) + if (r->qstream) { + r->qstream->skip_data = 1; + + /* disable stream read to avoid pointless data events */ + ngx_http_v3_stop_stream_read(r->qstream, 0); + + return NGX_OK; + } +#endif + if (ngx_http_test_expect(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -921,6 +944,9 @@ #if (NGX_HTTP_V2) || r->stream != NULL #endif +#if (NGX_HTTP_V3) + || r->qstream != NULL +#endif ) { return NGX_OK; @@ -960,6 +986,13 @@ static ngx_int_t ngx_http_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { + +#if (NGX_HTTP_V3) + if (r->qstream) { + return ngx_http_v3_request_body_filter(r, in); + } +#endif + if (r->headers_in.chunked) { return ngx_http_request_body_chunked_filter(r, in); diff --color -uNr a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/ngx_http_request.c 2023-03-12 11:07:46.631813037 +0800 @@ -62,6 +62,10 @@ static void ngx_http_ssl_handshake_handler(ngx_connection_t *c); #endif +#if (NGX_HTTP_V3) +static void ngx_http_quic_handshake(ngx_event_t *rev); +#endif + static char *ngx_http_client_errors[] = { @@ -348,6 +352,20 @@ c->log->action = "reading PROXY protocol"; } +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + hc->quic = 1; + c->log->action = "QUIC handshaking"; + + /* We already have a UDP packet in the connection buffer, so we don't + * need to wait for another read event to kick-off the handshake. */ + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_http_quic_handshake(rev); + return; + } +#endif + if (rev->ready) { /* the deferred accept(), iocp */ @@ -803,8 +821,9 @@ c->ssl->no_wait_shutdown = 1; -#if (NGX_HTTP_V2 \ - && defined TLSEXT_TYPE_application_layer_protocol_negotiation) +#if ((NGX_HTTP_V2 || NGX_HTTP_V3) \ + && (defined TLSEXT_TYPE_application_layer_protocol_negotiation \ + || defined TLSEXT_TYPE_next_proto_neg)) { unsigned int len; const unsigned char *data; @@ -812,15 +831,33 @@ hc = c->data; - if (hc->addr_conf->http2) { + if (hc->addr_conf->http2 || hc->addr_conf->quic) { SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + } + +#if (NGX_HTTP_V2) + if (hc->addr_conf->http2) { if (len == 2 && data[0] == 'h' && data[1] == '2') { ngx_http_v2_init(c->read); return; } } +#endif + +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + if (len >= 2 && data[0] == 'h' && data[1] == '3') { + ngx_http_v3_init(c->read); + return; + } + + ngx_http_close_connection(c); + return; + } +#endif + } #endif @@ -1045,6 +1082,70 @@ #endif +#if (NGX_HTTP_V3) + +static void +ngx_http_quic_handshake(ngx_event_t *rev) +{ + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_connection_t *hc; + ngx_http_v3_srv_conf_t *qscf; + ngx_http_ssl_srv_conf_t *sscf; + ngx_http_core_srv_conf_t *cscf; + + c = rev->data; + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "http check quic handshake"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_http_close_connection(c); + return; + } + + if (c->close) { + ngx_http_close_connection(c); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "https quic handshake"); + + sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, + ngx_http_ssl_module); + + if (ngx_ssl_create_connection(&sscf->ssl, c, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + qscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + + if (ngx_quic_create_connection(&qscf->quic, c) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + rc = ngx_quic_handshake(c); + + if (rc == NGX_AGAIN) { + + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + + c->ssl->handler = ngx_http_ssl_handshake_handler; + return; + } + + ngx_http_ssl_handshake_handler(c); +} + +#endif + static void ngx_http_process_request_line(ngx_event_t *rev) @@ -2710,6 +2811,13 @@ } #endif +#if (NGX_HTTP_V3) + if (r->qstream) { + ngx_http_close_request(r, 0); + return; + } +#endif + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->main->count != 1) { @@ -2924,6 +3032,19 @@ #endif +#if (NGX_HTTP_V3) + + if (r->qstream) { + if (c->error) { + err = 0; + goto closed; + } + + return; + } + +#endif + #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { @@ -3616,7 +3737,15 @@ } #endif +#if (NGX_HTTP_V3) + if (r->qstream) { + ngx_http_v3_close_stream(r->qstream, rc); + return; + } +#endif + ngx_http_free_request(r, rc); + ngx_http_close_connection(c); } @@ -3737,6 +3866,17 @@ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "close http connection: %d", c->fd); +#if (NGX_HTTP_V3) + + if (c->quic) { + if (ngx_quic_shutdown(c) == NGX_AGAIN) { + c->quic->handler = ngx_http_close_connection; + return; + } + } + +#endif + #if (NGX_HTTP_SSL) if (c->ssl) { diff --color -uNr a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/ngx_http_request.h 2023-03-12 11:07:31.421412029 +0800 @@ -24,6 +24,7 @@ #define NGX_HTTP_VERSION_10 1000 #define NGX_HTTP_VERSION_11 1001 #define NGX_HTTP_VERSION_20 2000 +#define NGX_HTTP_VERSION_3 3000 #define NGX_HTTP_UNKNOWN 0x00000001 #define NGX_HTTP_GET 0x00000002 @@ -329,6 +330,7 @@ ngx_chain_t *free; unsigned ssl:1; + unsigned quic:1; unsigned proxy_protocol:1; } ngx_http_connection_t; @@ -451,6 +453,7 @@ ngx_http_connection_t *http_connection; ngx_http_v2_stream_t *stream; + ngx_http_v3_stream_t *qstream; ngx_http_log_handler_pt log_handler; diff --color -uNr a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/ngx_http_upstream.c 2023-03-12 11:07:31.422412056 +0800 @@ -521,6 +521,13 @@ } #endif +#if (NGX_HTTP_V3) + if (r->qstream) { + ngx_http_upstream_init_request(r); + return; + } +#endif + if (c->read->timer_set) { ngx_del_timer(c->read); } @@ -1353,6 +1360,12 @@ return; } #endif + +#if (NGX_HTTP_V3) + if (r->qstream) { + return; + } +#endif #if (NGX_HAVE_KQUEUE) diff --color -uNr a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c --- a/src/http/v2/ngx_http_v2.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/v2/ngx_http_v2.c 2023-03-12 11:07:39.515625425 +0800 @@ -274,6 +274,8 @@ h2c->frame_size = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + h2c->max_hpack_table_size = NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE; + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); h2c->concurrent_pushes = h2scf->concurrent_pushes; @@ -2283,6 +2285,14 @@ case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING: h2c->table_update = 1; + + if (value > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { + h2c->max_hpack_table_size = NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE; + } else { + h2c->max_hpack_table_size = value; + } + + h2c->indicate_resize = 1; break; default: diff --color -uNr a/src/http/v2/ngx_http_v2_encode.c b/src/http/v2/ngx_http_v2_encode.c --- a/src/http/v2/ngx_http_v2_encode.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_encode.c 2023-03-12 11:07:39.515625425 +0800 @@ -10,7 +10,7 @@ #include -static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, +u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); @@ -40,7 +40,7 @@ } -static u_char * +u_char * ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) { if (value < prefix) { diff --color -uNr a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c --- a/src/http/v2/ngx_http_v2_filter_module.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_filter_module.c 2023-03-12 11:07:39.516625451 +0800 @@ -23,10 +23,53 @@ #define ngx_http_v2_literal_size(h) \ (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1) +#define ngx_http_v2_indexed(i) (128 + (i)) +#define ngx_http_v2_inc_indexed(i) (64 + (i)) + +#define NGX_HTTP_V2_ENCODE_RAW 0 +#define NGX_HTTP_V2_ENCODE_HUFF 0x80 + +#define NGX_HTTP_V2_AUTHORITY_INDEX 1 +#define NGX_HTTP_V2_METHOD_GET_INDEX 2 +#define NGX_HTTP_V2_PATH_INDEX 4 + +#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6 +#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7 + +#define NGX_HTTP_V2_STATUS_INDEX 8 +#define NGX_HTTP_V2_STATUS_200_INDEX 8 +#define NGX_HTTP_V2_STATUS_204_INDEX 9 +#define NGX_HTTP_V2_STATUS_206_INDEX 10 +#define NGX_HTTP_V2_STATUS_304_INDEX 11 +#define NGX_HTTP_V2_STATUS_400_INDEX 12 +#define NGX_HTTP_V2_STATUS_404_INDEX 13 +#define NGX_HTTP_V2_STATUS_500_INDEX 14 + +#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 +#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 +#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 +#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 +#define NGX_HTTP_V2_DATE_INDEX 33 +#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 +#define NGX_HTTP_V2_LOCATION_INDEX 46 +#define NGX_HTTP_V2_SERVER_INDEX 54 +#define NGX_HTTP_V2_USER_AGENT_INDEX 58 +#define NGX_HTTP_V2_VARY_INDEX 59 #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 +static const struct { + u_char *name; + u_char const len; +} push_header[] = { + { (u_char*)":authority" , 10 }, + { (u_char*)"accept-encoding" , 15 }, + { (u_char*)"accept-language" , 15 }, + { (u_char*)"user-agent" , 10 } +}; + + typedef struct { ngx_str_t name; u_char index; @@ -155,11 +198,9 @@ #endif static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER); - static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)]; static size_t nginx_ver_build_len = ngx_http_v2_literal_size(NGINX_VER_BUILD); - static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)]; stream = r->stream; @@ -435,7 +476,7 @@ } tmp = ngx_palloc(r->pool, tmp_len); - pos = ngx_pnalloc(r->pool, len); + pos = ngx_pnalloc(r->pool, len + 15 + 1); if (pos == NULL || tmp == NULL) { return NGX_ERROR; @@ -443,11 +484,16 @@ start = pos; - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; + h2c = r->stream->connection; + + if (h2c->indicate_resize) { + *pos = 32; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), + h2c->max_hpack_table_size); + h2c->indicate_resize = 0; +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_table_resize(h2c); +#endif } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, @@ -458,67 +504,28 @@ *pos++ = status; } else { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); - *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3; - pos = ngx_sprintf(pos, "%03ui", r->headers_out.status); + ngx_sprintf(pos + 8, "%O3ui", r->headers_out.status); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)":status", + sizeof(":status") - 1, pos + 8, 3, tmp); } if (r->headers_out.server == NULL) { - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: %s\"", - NGINX_VER); - - } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: %s\"", - NGINX_VER_BUILD); - - } else { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: nginx\""); - } - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); - - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - if (nginx_ver[0] == '\0') { - p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER, - sizeof(NGINX_VER) - 1, tmp); - nginx_ver_len = p - nginx_ver; - } - - pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len); + pos = ngx_http_v2_write_header_str("server", NGINX_VER); } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - if (nginx_ver_build[0] == '\0') { - p = ngx_http_v2_write_value(nginx_ver_build, - (u_char *) NGINX_VER_BUILD, - sizeof(NGINX_VER_BUILD) - 1, tmp); - nginx_ver_build_len = p - nginx_ver_build; - } - - pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len); + pos = ngx_http_v2_write_header_str("server", NGINX_VER_BUILD); } else { - pos = ngx_cpymem(pos, nginx, sizeof(nginx)); + pos = ngx_http_v2_write_header_str("server", "nginx"); } } if (r->headers_out.date == NULL) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"date: %V\"", - &ngx_cached_http_time); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); - pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data, - ngx_cached_http_time.len, tmp); + pos = ngx_http_v2_write_header_tbl("date", ngx_cached_http_time); } if (r->headers_out.content_type.len) { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); - if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { @@ -544,64 +551,36 @@ r->headers_out.content_type.data = p - len; } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"content-type: %V\"", - &r->headers_out.content_type); - - pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data, - r->headers_out.content_type.len, tmp); + pos = ngx_http_v2_write_header_tbl("content-type", + r->headers_out.content_type); } if (r->headers_out.content_length == NULL && r->headers_out.content_length_n >= 0) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"content-length: %O\"", - r->headers_out.content_length_n); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX); - - p = pos; - pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n); - *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1); + p = ngx_sprintf(pos + 15, "%O", r->headers_out.content_length_n); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"content-length", + sizeof("content-length") - 1, pos + 15, + p - (pos + 15), tmp); } if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); - - ngx_http_time(pos, r->headers_out.last_modified_time); + ngx_http_time(pos + 14, r->headers_out.last_modified_time); len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"last-modified: %*s\"", - len, pos); - - /* - * Date will always be encoded using huffman in the temporary buffer, - * so it's safe here to use src and dst pointing to the same address. - */ - pos = ngx_http_v2_write_value(pos, pos, len, tmp); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"last-modified", + sizeof("last-modified") - 1, pos + 14, + len, tmp); } if (r->headers_out.location && r->headers_out.location->value.len) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"location: %V\"", - &r->headers_out.location->value); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); - pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data, - r->headers_out.location->value.len, tmp); + pos = ngx_http_v2_write_header_tbl("location", r->headers_out.location->value); } #if (NGX_HTTP_GZIP) if (r->gzip_vary) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"vary: Accept-Encoding\""); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); - pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding)); + pos = ngx_http_v2_write_header_str("vary", "Accept-Encoding"); } #endif @@ -624,23 +603,10 @@ continue; } -#if (NGX_DEBUG) - if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { - ngx_strlow(tmp, header[i].key.data, header[i].key.len); - - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"%*s: %V\"", - header[i].key.len, tmp, &header[i].value); - } -#endif - - *pos++ = 0; - - pos = ngx_http_v2_write_name(pos, header[i].key.data, - header[i].key.len, tmp); + pos = ngx_http_v2_write_header(h2c, pos, header[i].key.data, + header[i].key.len, header[i].value.data, + header[i].value.len, tmp); - pos = ngx_http_v2_write_value(pos, header[i].value.data, - header[i].value.len, tmp); } fin = r->header_only @@ -997,6 +963,7 @@ for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { len += binary[i].len; + len += push_header[i].len + 1; } pos = ngx_pnalloc(r->pool, len); @@ -1006,12 +973,17 @@ start = pos; - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; - } + h2c = r->stream->connection; + + if (h2c->indicate_resize) { + *pos = 32; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), + h2c->max_hpack_table_size); + h2c->indicate_resize = 0; +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_table_resize(h2c); +#endif + } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":method: GET\""); @@ -1021,8 +993,7 @@ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":path: %V\"", path); - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); - pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp); + pos = ngx_http_v2_write_header_pot(":path", path); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":scheme: %V\"", &r->schema); @@ -1047,11 +1018,15 @@ continue; } + value = &(*h)->value; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \"%V: %V\"", &ph[i].name, &(*h)->value); - pos = ngx_cpymem(pos, binary[i].data, binary[i].len); + pos = ngx_http_v2_write_header(h2c, pos, + push_header[i].name, push_header[i].len, value->data, value->len, + tmp); } frame = ngx_http_v2_create_push_frame(r, start, pos); diff --color -uNr a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h --- a/src/http/v2/ngx_http_v2.h 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/v2/ngx_http_v2.h 2023-03-12 11:07:39.516625451 +0800 @@ -51,6 +51,14 @@ #define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1) #define NGX_HTTP_V2_DEFAULT_WINDOW 65535 +#define HPACK_ENC_HTABLE_SZ 128 /* better to keep a PoT < 64k */ +#define HPACK_ENC_HTABLE_ENTRIES ((HPACK_ENC_HTABLE_SZ * 100) / 128) +#define HPACK_ENC_DYNAMIC_KEY_TBL_SZ 10 /* 10 is sufficient for most */ +#define HPACK_ENC_MAX_ENTRY 512 /* longest header size to match */ + +#define NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE 4096 +#define NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE 16384 /* < 64k */ + #define NGX_HTTP_V2_DEFAULT_WEIGHT 16 @@ -114,6 +122,46 @@ } ngx_http_v2_hpack_t; +#if (NGX_HTTP_V2_HPACK_ENC) +typedef struct { + uint64_t hash_val; + uint32_t index; + uint16_t pos; + uint16_t klen, vlen; + uint16_t size; + uint16_t next; +} ngx_http_v2_hpack_enc_entry_t; + + +typedef struct { + uint64_t hash_val; + uint32_t index; + uint16_t pos; + uint16_t klen; +} ngx_http_v2_hpack_name_entry_t; + + +typedef struct { + size_t size; /* size as defined in RFC 7541 */ + uint32_t top; /* the last entry */ + uint32_t pos; + uint16_t n_elems; /* number of elements */ + uint16_t base; /* index of the oldest entry */ + uint16_t last; /* index of the newest entry */ + + /* hash table for dynamic entries, instead using a generic hash table, + which would be too slow to process a significant amount of headers, + this table is not determenistic, and might ocasionally fail to insert + a value, at the cost of slightly worse compression, but significantly + faster performance */ + ngx_http_v2_hpack_enc_entry_t htable[HPACK_ENC_HTABLE_SZ]; + ngx_http_v2_hpack_name_entry_t heads[HPACK_ENC_DYNAMIC_KEY_TBL_SZ]; + u_char storage[NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE + + HPACK_ENC_MAX_ENTRY]; +} ngx_http_v2_hpack_enc_t; +#endif + + struct ngx_http_v2_connection_s { ngx_connection_t *connection; ngx_http_connection_t *http_connection; @@ -135,6 +183,8 @@ size_t frame_size; + size_t max_hpack_table_size; + ngx_queue_t waiting; ngx_http_v2_state_t state; @@ -164,6 +214,11 @@ unsigned blocked:1; unsigned goaway:1; unsigned push_disabled:1; + unsigned indicate_resize:1; + +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_hpack_enc_t hpack_enc; +#endif }; @@ -207,6 +262,8 @@ ngx_array_t *cookies; + size_t header_limit; + ngx_pool_t *pool; unsigned waiting:1; @@ -413,4 +470,35 @@ u_char *tmp, ngx_uint_t lower); +u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, + u_char *tmp, ngx_uint_t lower); + +u_char * +ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); + +#define ngx_http_v2_write_name(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 1) +#define ngx_http_v2_write_value(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 0) + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, u_char *value, size_t value_len, + u_char *tmp); + +void +ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c); + +#define ngx_http_v2_write_header_str(key, value) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + (u_char *) value, sizeof(value) - 1, tmp); + +#define ngx_http_v2_write_header_tbl(key, val) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + val.data, val.len, tmp); + +#define ngx_http_v2_write_header_pot(key, val) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + val->data, val->len, tmp); + #endif /* _NGX_HTTP_V2_H_INCLUDED_ */ diff --color -uNr a/src/http/v2/ngx_http_v2_table.c b/src/http/v2/ngx_http_v2_table.c --- a/src/http/v2/ngx_http_v2_table.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_table.c 2023-03-12 11:07:39.517625477 +0800 @@ -361,3 +361,434 @@ return NGX_OK; } + + +#if (NGX_HTTP_V2_HPACK_ENC) + +static ngx_int_t +hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len); + +static ngx_int_t +hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, + uint8_t *key, size_t key_len); + + +void +ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c) +{ + ngx_http_v2_hpack_enc_entry_t *table; + uint64_t idx; + + table = h2c->hpack_enc.htable; + + while (h2c->hpack_enc.size > h2c->max_hpack_table_size) { + idx = h2c->hpack_enc.base; + h2c->hpack_enc.base = table[idx].next; + h2c->hpack_enc.size -= table[idx].size; + table[idx].hash_val = 0; + h2c->hpack_enc.n_elems--; + } +} + + +/* checks if a header is in the hpack table - if so returns the table entry, + otherwise encodes and inserts into the table and returns 0, + if failed to insert into table, returns -1 */ +static ngx_int_t +ngx_http_v2_table_encode_strings(ngx_http_v2_connection_t *h2c, + size_t key_len, size_t val_len, uint8_t *key, uint8_t *val, + ngx_int_t *header_idx) +{ + uint64_t hash_val, key_hash, idx, lru; + int i; + size_t size = key_len + val_len + 32; + uint8_t *storage = h2c->hpack_enc.storage; + + ngx_http_v2_hpack_enc_entry_t *table; + ngx_http_v2_hpack_name_entry_t *name; + + *header_idx = NGX_ERROR; + /* step 1: compute the hash value of header */ + if (size > HPACK_ENC_MAX_ENTRY || size > h2c->max_hpack_table_size) { + return NGX_ERROR; + } + + key_hash = ngx_murmur_hash2_64(key, key_len, 0x01234); + hash_val = ngx_murmur_hash2_64(val, val_len, key_hash); + + if (hash_val == 0) { + return NGX_ERROR; + } + + /* step 2: check if full header in the table */ + idx = hash_val; + i = -1; + while (idx) { + /* at most 8 locations are checked, but most will be done in 1 or 2 */ + table = &h2c->hpack_enc.htable[idx % HPACK_ENC_HTABLE_SZ]; + if (table->hash_val == hash_val + && table->klen == key_len + && table->vlen == val_len + && ngx_memcmp(key, storage + table->pos, key_len) == 0 + && ngx_memcmp(val, storage + table->pos + key_len, val_len) == 0) + { + return (h2c->hpack_enc.top - table->index) + 61; + } + + if (table->hash_val == 0 && i == -1) { + i = idx % HPACK_ENC_HTABLE_SZ; + break; + } + + idx >>= 8; + } + + /* step 3: check if key is in one of the tables */ + *header_idx = hpack_get_static_index(h2c, key, key_len); + + if (i == -1) { + return NGX_ERROR; + } + + if (*header_idx == NGX_ERROR) { + *header_idx = hpack_get_dynamic_index(h2c, key_hash, key, key_len); + } + + /* step 4: store the new entry */ + table = h2c->hpack_enc.htable; + + if (h2c->hpack_enc.top == 0xffffffff) { + /* just to be on the safe side, avoid overflow */ + ngx_memset(&h2c->hpack_enc, 0, sizeof(ngx_http_v2_hpack_enc_t)); + } + + while ((h2c->hpack_enc.size + size > h2c->max_hpack_table_size) + || h2c->hpack_enc.n_elems == HPACK_ENC_HTABLE_ENTRIES) { + /* make space for the new entry first */ + idx = h2c->hpack_enc.base; + h2c->hpack_enc.base = table[idx].next; + h2c->hpack_enc.size -= table[idx].size; + table[idx].hash_val = 0; + h2c->hpack_enc.n_elems--; + } + + table[i] = (ngx_http_v2_hpack_enc_entry_t){.hash_val = hash_val, + .index = h2c->hpack_enc.top, + .pos = h2c->hpack_enc.pos, + .klen = key_len, + .vlen = val_len, + .size = size, + .next = 0}; + + table[h2c->hpack_enc.last].next = i; + if (h2c->hpack_enc.n_elems == 0) { + h2c->hpack_enc.base = i; + } + + h2c->hpack_enc.last = i; + h2c->hpack_enc.top++; + h2c->hpack_enc.size += size; + h2c->hpack_enc.n_elems++; + + /* update header name lookup */ + if (*header_idx == NGX_ERROR ) { + lru = h2c->hpack_enc.top; + + for (i=0; ihpack_enc.heads[i]; + + if ( name->hash_val == 0 || (name->hash_val == key_hash + && ngx_memcmp(storage + name->pos, key, key_len) == 0) ) + { + name->hash_val = key_hash; + name->pos = h2c->hpack_enc.pos; + name->index = h2c->hpack_enc.top - 1; + break; + } + + if (lru > name->index) { + lru = name->index; + idx = i; + } + } + + if (i == HPACK_ENC_DYNAMIC_KEY_TBL_SZ) { + name = &h2c->hpack_enc.heads[idx]; + name->hash_val = hash_val; + name->pos = h2c->hpack_enc.pos; + name->index = h2c->hpack_enc.top - 1; + } + } + + ngx_memcpy(storage + h2c->hpack_enc.pos, key, key_len); + ngx_memcpy(storage + h2c->hpack_enc.pos + key_len, val, val_len); + + h2c->hpack_enc.pos += size; + if (h2c->hpack_enc.pos > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { + h2c->hpack_enc.pos = 0; + } + + return NGX_OK; +} + + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, + u_char *value, size_t value_len, + u_char *tmp) +{ + ngx_int_t idx, header_idx; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 output header: %*s: %*s", key_len, key, value_len, + value); + + /* attempt to find the value in the dynamic table */ + idx = ngx_http_v2_table_encode_strings(h2c, key_len, value_len, key, value, + &header_idx); + + if (idx > 0) { + /* positive index indicates success */ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Indexed Header Field: %ud", idx); + + *pos = 128; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), idx); + + } else { + + if (header_idx == NGX_ERROR) { /* if key is not present */ + + if (idx == NGX_ERROR) { /* if header was not added */ + *pos++ = 0; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field without" + " Indexing — New Name"); + } else { /* if header was added */ + *pos++ = 64; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field with " + "Incremental Indexing — New Name"); + } + + pos = ngx_http_v2_write_name(pos, key, key_len, tmp); + + } else { /* if key is present */ + + if (idx == NGX_ERROR) { + *pos = 0; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(4), header_idx); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field without" + " Indexing — Indexed Name: %ud", header_idx); + } else { + *pos = 64; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(6), header_idx); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field with " + "Incremental Indexing — Indexed Name: %ud", header_idx); + } + } + + pos = ngx_http_v2_write_value(pos, value, value_len, tmp); + } + + return pos; +} + + +static ngx_int_t +hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, + uint8_t *key, size_t key_len) +{ + ngx_http_v2_hpack_name_entry_t *name; + int i; + + for (i=0; ihpack_enc.heads[i]; + + if (name->hash_val == key_hash + && ngx_memcmp(h2c->hpack_enc.storage + name->pos, key, key_len) == 0) + { + if (name->index >= h2c->hpack_enc.top - h2c->hpack_enc.n_elems) { + return (h2c->hpack_enc.top - name->index) + 61; + } + break; + } + } + + return NGX_ERROR; +} + + +/* decide if a given header is present in the static dictionary, this could be + done in several ways, but it seems the fastest one is "exhaustive" search */ +static ngx_int_t +hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len) +{ + /* the static dictionary of response only headers, + although response headers can be put by origin, + that would be rare */ + static const struct { + u_char len; + const u_char val[28]; + u_char idx; + } server_headers[] = { + { 3, "age", 21},//0 + { 3, "via", 60}, + { 4, "date", 33},//2 + { 4, "etag", 34}, + { 4, "link", 45}, + { 4, "vary", 59}, + { 5, "allow", 22},//6 + { 6, "server", 54},//7 + { 7, "expires", 36},//8 + { 7, "refresh", 52}, + { 8, "location", 46},//10 + {10, "set-cookie", 55},//11 + {11, "retry-after", 53},//12 + {12, "content-type", 31},//13 + {13, "content-range", 30},//14 + {13, "accept-ranges", 18}, + {13, "cache-control", 24}, + {13, "last-modified", 44}, + {14, "content-length", 28},//18 + {16, "content-encoding", 26},//19 + {16, "content-language", 27}, + {16, "content-location", 29}, + {16, "www-authenticate", 61}, + {17, "transfer-encoding", 57},//23 + {18, "proxy-authenticate", 48},//24 + {19, "content-disposition", 25},//25 + {25, "strict-transport-security", 56},//26 + {27, "access-control-allow-origin", 20},//27 + {99, "", 99}, + }, *header; + + /* for a given length, where to start the search + since minimal length is 3, the table has a -3 + offset */ + static const int8_t start_at[] = { + [3-3] = 0, + [4-3] = 2, + [5-3] = 6, + [6-3] = 7, + [7-3] = 8, + [8-3] = 10, + [9-3] = -1, + [10-3] = 11, + [11-3] = 12, + [12-3] = 13, + [13-3] = 14, + [14-3] = 18, + [15-3] = -1, + [16-3] = 19, + [17-3] = 23, + [18-3] = 24, + [19-3] = 25, + [20-3] = -1, + [21-3] = -1, + [22-3] = -1, + [23-3] = -1, + [24-3] = -1, + [25-3] = 26, + [26-3] = -1, + [27-3] = 27, + }; + + uint64_t pref; + size_t save_len = len, i; + int8_t start; + + /* early exit for out of bounds lengths */ + if (len < 3 || len > 27) { + return NGX_ERROR; + } + + start = start_at[len - 3]; + if (start == -1) { + /* exit for non existent lengths */ + return NGX_ERROR; + } + + header = &server_headers[start_at[len - 3]]; + + /* load first 8 bytes of key, for fast comparison */ + if (len < 8) { + pref = 0; + if (len >= 4) { + pref = *(uint32_t *)(val + len - 4) | 0x20202020; + len -= 4; + } + while (len > 0) { /* 3 iterations at most */ + pref = (pref << 8) ^ (val[len - 1] | 0x20); + len--; + } + } else { + pref = *(uint64_t *)val | 0x2020202020202020; + len -= 8; + } + + /* iterate over headers with the right length */ + while (header->len == save_len) { + /* quickly compare the first 8 bytes, most tests will end here */ + if (pref != *(uint64_t *) header->val) { + header++; + continue; + } + + if (len == 0) { + /* len == 0, indicates prefix held the entire key */ + return header->idx; + } + /* for longer keys compare the rest */ + i = 1 + (save_len + 7) % 8; /* align so we can compare in quadwords */ + + while (i + 8 <= save_len) { /* 3 iterations at most */ + if ( *(uint64_t *)&header->val[i] + != (*(uint64_t *) &val[i]| 0x2020202020202020) ) + { + header++; + i = 0; + break; + } + i += 8; + } + + if (i == 0) { + continue; + } + + /* found the corresponding entry in the static dictionary */ + return header->idx; + } + + return NGX_ERROR; +} + +#else + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, + u_char *value, size_t value_len, + u_char *tmp) +{ + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 output header: %*s: %*s", key_len, key, value_len, + value); + + *pos++ = 64; + pos = ngx_http_v2_write_name(pos, key, key_len, tmp); + pos = ngx_http_v2_write_value(pos, value, value_len, tmp); + + return pos; +} + +#endif diff --color -uNr a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c --- a/src/http/v3/ngx_http_v3.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3.c 2023-03-12 11:07:31.423412082 +0800 @@ -0,0 +1,2230 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#include +#include +#include +#include + + +typedef struct { + ngx_str_t name; + ngx_uint_t offset; + ngx_uint_t hash; + ngx_http_header_t *hh; +} ngx_http_v3_parse_header_t; + + +/* errors */ +#define NGX_HTTP_V3_NO_ERROR 0x0100 +#define NGX_HTTP_V3_PROTOCOL_ERROR 0x0101 +#define NGX_HTTP_V3_INTERNAL_ERROR 0x0102 + + +static void ngx_http_v3_handler(ngx_connection_t *c); + +static void ngx_http_v3_idle_handler(ngx_connection_t *c); + +static void ngx_http_v3_handle_connection(ngx_http_v3_connection_t *h3c); + +static ngx_http_v3_stream_t *ngx_http_v3_stream_lookup( + ngx_http_v3_connection_t *h3c, ngx_uint_t stream_id); +static ngx_http_v3_stream_t *ngx_http_v3_create_stream( + ngx_http_v3_connection_t *h3c); +static void ngx_http_v3_close_stream_handler(ngx_event_t *ev); + +static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r, + ngx_http_v3_header_t *header); +static ngx_int_t ngx_http_v3_pseudo_header(ngx_http_request_t *r, + ngx_http_v3_header_t *header); +static ngx_int_t ngx_http_v3_parse_path(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v3_parse_method(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v3_parse_scheme(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v3_parse_authority(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, + ngx_http_v3_parse_header_t *header, ngx_str_t *value); +static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r, + ngx_http_v3_header_t *header); +static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_construct_request_line(ngx_http_request_t *r); + +static void ngx_http_v3_run_request(ngx_http_request_t *r); + +static ssize_t ngx_http_v3_recv_body(ngx_connection_t *c, u_char *buf, + size_t size); +static ngx_chain_t *ngx_http_v3_send_chain(ngx_connection_t *fc, + ngx_chain_t *in, off_t limit); + +static void ngx_http_v3_finalize_connection(ngx_http_v3_connection_t *h3c, + ngx_uint_t status); + +static void ngx_http_v3_pool_cleanup(void *data); + + +static ngx_http_v3_parse_header_t ngx_http_v3_parse_headers[] = { + { ngx_string("host"), + offsetof(ngx_http_headers_in_t, host), 0, NULL }, + + { ngx_string("accept-encoding"), + offsetof(ngx_http_headers_in_t, accept_encoding), 0, NULL }, + + { ngx_string("accept-language"), + offsetof(ngx_http_headers_in_t, accept_language), 0, NULL }, + + { ngx_string("user-agent"), + offsetof(ngx_http_headers_in_t, user_agent), 0, NULL }, + + { ngx_null_string, 0, 0, NULL } +}; + + +void +ngx_http_v3_init(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_connection_t *h3c; + + c = rev->data; + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "init http3 connection"); + + c->log->action = "processing HTTP/3 connection"; + + h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t)); + if (h3c == NULL) { + ngx_http_close_connection(c); + return; + } + + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + + h3c->h3 = quiche_h3_conn_new_with_transport(c->quic->conn, h3scf->http3); + if (h3c->h3 == NULL) { + ngx_http_close_connection(c); + return; + } + + h3c->http_connection = hc; + + h3c->connection = c; + + h3c->pool = c->pool; + + c->data = h3c; + + c->quic->handler = ngx_http_v3_handler; + + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_v3_pool_cleanup; + cln->data = h3c; + + ngx_rbtree_init(&h3c->streams, &h3c->streams_sentinel, + ngx_rbtree_insert_value); +} + + +static int +ngx_http_v3_for_each_header(uint8_t *name, size_t name_len, + uint8_t *value, size_t value_len, void *argp) +{ + ngx_int_t rc; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_request_t *r; + ngx_http_v3_header_t header; + ngx_http_core_srv_conf_t *cscf; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + r = argp; + + /* Duplicate the header name because we don't own it. */ + header.name.data = ngx_pnalloc(r->pool, name_len); + if (header.name.data == NULL) { + return NGX_ERROR; + } + header.name.len = name_len; + + ngx_memcpy(header.name.data, name, name_len); + + /* Duplicate the header value because we don't own it. Some of the + * functions that process headers require a NULL-terminated string, + * so allocate enough memory for that. */ + header.value.data = ngx_pcalloc(r->pool, value_len + 1); + if (header.value.data == NULL) { + return NGX_ERROR; + } + header.value.len = value_len; + + ngx_memcpy(header.value.data, value, value_len); + + if (ngx_http_v3_validate_header(r, &header) != NGX_OK) { + return NGX_ERROR; + } + + /* Check for pseudo-header. */ + if (header.name.data[0] == ':') { + rc = ngx_http_v3_pseudo_header(r, &header); + + if (rc == NGX_OK) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \":%V: %V\"", + &header.name, &header.value); + + return NGX_OK; + } + + return NGX_ERROR; + } + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%V\"", &header.name); + + return NGX_ERROR; + } + } + + /* Handle Cookie header separately. Not sure why, but the HTTP/2 code does + * the same. */ + if (header.name.len == cookie.len + && ngx_memcmp(header.name.data, cookie.data, cookie.len) == 0) + { + if (ngx_http_v3_cookie(r, &header) != NGX_OK) { + return NGX_ERROR; + } + + } else { + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key.len = header.name.len; + h->key.data = header.name.data; + + /* + * TODO Optimization: precalculate hash + * and handler for indexed headers. + */ + h->hash = ngx_hash_key(h->key.data, h->key.len); + + h->value.len = header.value.len; + h->value.data = header.value.data; + + h->lowcase_key = h->key.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \"%V: %V\"", + &header.name, &header.value); + + return NGX_OK; +} + + +static void +ngx_http_v3_process_headers(ngx_connection_t *c, quiche_h3_event *ev, + int64_t stream_id) +{ + int rc; + ngx_http_v3_stream_t *stream; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_connection_t *h3c; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 process headers"); + + h3c = c->data; + + h3scf = ngx_http_get_module_srv_conf(h3c->http_connection->conf_ctx, + ngx_http_v3_module); + + if (h3c->connection->requests >= h3scf->max_requests) { + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_NO_ERROR); + return; + } + + /* Create a new stream to handle the incoming request. */ + stream = ngx_http_v3_create_stream(h3c); + if (stream == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create HTTP/3 stream"); + + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_INTERNAL_ERROR); + return; + } + + stream->id = stream_id; + + stream->node.key = stream_id; + + ngx_rbtree_insert(&h3c->streams, &stream->node); + + /* Populate ngx_http_request_t from raw HTTP/3 headers. */ + rc = quiche_h3_event_for_each_header(ev, + ngx_http_v3_for_each_header, stream->request); + + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "received invalid HTTP/3 headers"); + + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_INTERNAL_ERROR); + return; + } + + stream->in_closed = !quiche_h3_event_headers_has_body(ev); + + ngx_http_v3_run_request(stream->request); +} + + +static void +ngx_http_v3_process_blocked_streams(ngx_http_v3_connection_t *h3c) +{ + ngx_event_t *wev; + ngx_http_v3_stream_t *stream; + ngx_quic_connection_t *quic = h3c->connection->quic; + int64_t stream_id; + + while ((stream_id = quiche_conn_stream_writable_next(quic->conn)) >= 0) { + stream = ngx_http_v3_stream_lookup(h3c, stream_id); + + if (stream == NULL) { + continue; + } + + if (!stream->blocked) { + continue; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 stream unblocked %ui", stream->id); + + stream->blocked = 0; + + wev = stream->request->connection->write; + + wev->active = 0; + wev->ready = 1; + + if (!stream->headers_sent) { + ngx_http_v3_send_response(stream->request); + } + + if (!wev->delayed) { + wev->handler(wev); + } + } +} + + +static void +ngx_http_v3_handler(ngx_connection_t *c) +{ + ngx_chain_t out; + ngx_connection_t *fc; + ngx_http_request_t *r; + ngx_http_v3_connection_t *h3c; + ngx_http_v3_stream_t *stream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 handler"); + + h3c = c->data; + + if (c->read->timedout) { + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_PROTOCOL_ERROR); + return; + } + + if (c->error) { + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_INTERNAL_ERROR); + return; + } + + ngx_http_v3_process_blocked_streams(h3c); + + while (!c->error) { + quiche_h3_event *ev; + + int64_t stream_id = quiche_h3_conn_poll(h3c->h3, c->quic->conn, &ev); + if (stream_id == QUICHE_H3_ERR_DONE) { + break; + } + + if (stream_id < 0) { + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_PROTOCOL_ERROR); + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 event stream:%ui ev:%ui", stream_id, + quiche_h3_event_type(ev)); + + switch (quiche_h3_event_type(ev)) { + case QUICHE_H3_EVENT_HEADERS: { + ngx_http_v3_process_headers(c, ev, stream_id); + break; + } + + case QUICHE_H3_EVENT_DATA: { + /* Lookup stream. If there isn't one, it means it has already + * been closed, so ignore the event. */ + stream = ngx_http_v3_stream_lookup(h3c, stream_id); + + if (stream != NULL && !stream->in_closed) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 data"); + + ngx_post_event(stream->request->connection->read, + &ngx_posted_events); + } + + break; + } + + case QUICHE_H3_EVENT_FINISHED: { + /* Lookup stream. If there isn't one, it means it has already + * been closed, so ignore the event. */ + stream = ngx_http_v3_stream_lookup(h3c, stream_id); + + if (stream != NULL && !stream->in_closed) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 finished"); + + /* Flush request body that was buffered. */ + if (stream->request->request_body) { + out.buf = stream->request->request_body->buf; + out.next = NULL; + + ngx_http_v3_request_body_filter(stream->request, &out); + + ngx_post_event(stream->request->connection->read, + &ngx_posted_events); + } + + stream->in_closed = 1; + } + + break; + } + + case QUICHE_H3_EVENT_RESET: { + /* Lookup stream. If there isn't one, it means it has already + * been closed, so ignore the event. */ + stream = ngx_http_v3_stream_lookup(h3c, stream_id); + + if (stream != NULL && !stream->in_closed) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 reset"); + + r = stream->request; + fc = r->connection; + + fc->error = 1; + + ngx_post_event(stream->request->connection->read, + &ngx_posted_events); + + stream->in_closed = 1; + } + + break; + } + + case QUICHE_H3_EVENT_PRIORITY_UPDATE: + break; + + case QUICHE_H3_EVENT_DATAGRAM: + break; + + case QUICHE_H3_EVENT_GOAWAY: + break; + } + + quiche_h3_event_free(ev); + } + + ngx_http_v3_handle_connection(h3c); +} + + +static void +ngx_http_v3_idle_handler(ngx_connection_t *c) +{ + ngx_http_v3_connection_t *h3c; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 idle handler"); + + h3c = c->data; + + if (c->read->timedout) { + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_NO_ERROR); + return; + } + + if (c->error) { + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_INTERNAL_ERROR); + return; + } + + if (!quiche_conn_is_readable(c->quic->conn)) { + return; + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + c->quic->handler = ngx_http_v3_handler; + + ngx_http_v3_handler(c); +} + + +static void +ngx_http_v3_handle_connection(ngx_http_v3_connection_t *h3c) +{ + ngx_connection_t *c; + ngx_http_v3_srv_conf_t *h3scf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 handle connection"); + + c = h3c->connection; + + if (h3c->processing || c->error) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 connection is idle"); + + h3scf = ngx_http_get_module_srv_conf(h3c->http_connection->conf_ctx, + ngx_http_v3_module); + + c->quic->handler = ngx_http_v3_idle_handler; + + ngx_add_timer(c->read, h3scf->idle_timeout); +} + + +static ngx_http_v3_stream_t * +ngx_http_v3_create_stream(ngx_http_v3_connection_t *h3c) +{ + ngx_log_t *log; + ngx_event_t *rev, *wev; + ngx_connection_t *fc; + ngx_http_log_ctx_t *ctx; + ngx_http_request_t *r; + ngx_http_v3_stream_t *stream; + ngx_http_core_srv_conf_t *cscf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 create stream"); + + fc = h3c->free_fake_connections; + + if (fc) { + h3c->free_fake_connections = fc->data; + + rev = fc->read; + wev = fc->write; + log = fc->log; + ctx = log->data; + + } else { + fc = ngx_palloc(h3c->pool, sizeof(ngx_connection_t)); + if (fc == NULL) { + return NULL; + } + + rev = ngx_palloc(h3c->pool, sizeof(ngx_event_t)); + if (rev == NULL) { + return NULL; + } + + wev = ngx_palloc(h3c->pool, sizeof(ngx_event_t)); + if (wev == NULL) { + return NULL; + } + + log = ngx_palloc(h3c->pool, sizeof(ngx_log_t)); + if (log == NULL) { + return NULL; + } + + ctx = ngx_palloc(h3c->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + return NULL; + } + + ctx->connection = fc; + ctx->request = NULL; + ctx->current_request = NULL; + } + + ngx_memcpy(log, h3c->connection->log, sizeof(ngx_log_t)); + + log->data = ctx; + + ngx_memzero(rev, sizeof(ngx_event_t)); + + rev->data = fc; + rev->ready = 1; + rev->handler = ngx_http_v3_close_stream_handler; + rev->log = log; + + ngx_memcpy(wev, rev, sizeof(ngx_event_t)); + + wev->write = 1; + + ngx_memcpy(fc, h3c->connection, sizeof(ngx_connection_t)); + + fc->data = h3c->http_connection; + fc->quic = h3c->connection->quic; + fc->read = rev; + fc->write = wev; + fc->sent = 0; + fc->buffer = NULL; + fc->log = log; + fc->buffered = 0; + fc->sndlowat = 1; + fc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; + + fc->recv = ngx_http_v3_recv_body; + + fc->send_chain = ngx_http_v3_send_chain; + fc->need_last_buf = 1; + + r = ngx_http_create_request(fc); + if (r == NULL) { + return NULL; + } + + ngx_str_set(&r->http_protocol, "HTTP/3"); + + r->http_version = NGX_HTTP_VERSION_3; + r->valid_location = 1; + + fc->data = r; + h3c->connection->requests++; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_in = ngx_create_temp_buf(r->pool, + cscf->client_header_buffer_size); + if (r->header_in == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; + + stream = ngx_pcalloc(h3c->pool, sizeof(ngx_http_v3_stream_t)); + if (stream == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->qstream = stream; + + stream->request = r; + stream->connection = h3c; + + h3c->processing++; + + return stream; +} + + +static ngx_http_v3_stream_t * +ngx_http_v3_stream_lookup(ngx_http_v3_connection_t *h3c, ngx_uint_t stream_id) +{ + ngx_rbtree_node_t *node, *sentinel; + + node = h3c->streams.root; + sentinel = h3c->streams.sentinel; + + while (node != sentinel) { + + if (stream_id < node->key) { + node = node->left; + continue; + } + + if (stream_id > node->key) { + node = node->right; + continue; + } + + /* stream_id == node->key */ + + return (ngx_http_v3_stream_t *) node; + } + + /* not found */ + + return NULL; +} + + +/* The following functions are copied from the HTTP/2 module, and adapted to + * work independently. In theory we could refactor the HTTP/2 module to expose + * these functions, but that would be fairly invasive and likely cause more + * merge conflicts in the future. */ + + +static ngx_int_t +ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_http_v3_header_t *header) +{ + u_char ch; + ngx_uint_t i; + ngx_http_core_srv_conf_t *cscf; + + if (header->name.len == 0) { + return NGX_ERROR; + } + + r->invalid_header = 0; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + for (i = (header->name.data[0] == ':'); i != header->name.len; i++) { + ch = header->name.data[i]; + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + if (ch == '\0' || ch == LF || ch == CR || ch == ':' + || (ch >= 'A' && ch <= 'Z')) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", + &header->name); + + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + for (i = 0; i != header->value.len; i++) { + ch = header->value.data[i]; + + if (ch == '\0' || ch == LF || ch == CR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "invalid value: \"%V\"", + &header->name, &header->value); + + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_pseudo_header(ngx_http_request_t *r, ngx_http_v3_header_t *header) +{ + header->name.len--; + header->name.data++; + + switch (header->name.len) { + case 4: + if (ngx_memcmp(header->name.data, "path", sizeof("path") - 1) + == 0) + { + return ngx_http_v3_parse_path(r, &header->value); + } + + break; + + case 6: + if (ngx_memcmp(header->name.data, "method", sizeof("method") - 1) + == 0) + { + return ngx_http_v3_parse_method(r, &header->value); + } + + if (ngx_memcmp(header->name.data, "scheme", sizeof("scheme") - 1) + == 0) + { + return ngx_http_v3_parse_scheme(r, &header->value); + } + + break; + + case 9: + if (ngx_memcmp(header->name.data, "authority", sizeof("authority") - 1) + == 0) + { + return ngx_http_v3_parse_authority(r, &header->value); + } + + break; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent unknown pseudo-header \":%V\"", + &header->name); + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_http_v3_parse_path(ngx_http_request_t *r, ngx_str_t *value) +{ + if (r->unparsed_uri.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :path header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :path header"); + + return NGX_DECLINED; + } + + r->uri_start = value->data; + r->uri_end = value->data + value->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid :path header: \"%V\"", value); + + return NGX_DECLINED; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_request_uri() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_parse_method(ngx_http_request_t *r, ngx_str_t *value) +{ + size_t k, len; + ngx_uint_t n; + const u_char *p, *m; + + /* + * This array takes less than 256 sequential bytes, + * and if typical CPU cache line size is 64 bytes, + * it is prefetched for 4 load operations. + */ + static const struct { + u_char len; + const u_char method[11]; + uint32_t value; + } tests[] = { + { 3, "GET", NGX_HTTP_GET }, + { 4, "POST", NGX_HTTP_POST }, + { 4, "HEAD", NGX_HTTP_HEAD }, + { 7, "OPTIONS", NGX_HTTP_OPTIONS }, + { 8, "PROPFIND", NGX_HTTP_PROPFIND }, + { 3, "PUT", NGX_HTTP_PUT }, + { 5, "MKCOL", NGX_HTTP_MKCOL }, + { 6, "DELETE", NGX_HTTP_DELETE }, + { 4, "COPY", NGX_HTTP_COPY }, + { 4, "MOVE", NGX_HTTP_MOVE }, + { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, + { 4, "LOCK", NGX_HTTP_LOCK }, + { 6, "UNLOCK", NGX_HTTP_UNLOCK }, + { 5, "PATCH", NGX_HTTP_PATCH }, + { 5, "TRACE", NGX_HTTP_TRACE } + }, *test; + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :method header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :method header"); + + return NGX_DECLINED; + } + + r->method_name.len = value->len; + r->method_name.data = value->data; + + len = r->method_name.len; + n = sizeof(tests) / sizeof(tests[0]); + test = tests; + + do { + if (len == test->len) { + p = r->method_name.data; + m = test->method; + k = len; + + do { + if (*p++ != *m++) { + goto next; + } + } while (--k); + + r->method = test->value; + return NGX_OK; + } + + next: + test++; + + } while (--n); + + p = r->method_name.data; + + do { + if ((*p < 'A' || *p > 'Z') && *p != '_' && *p != '-') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", + &r->method_name); + + return NGX_DECLINED; + } + + p++; + + } while (--len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_parse_scheme(ngx_http_request_t *r, ngx_str_t *value) +{ + u_char c, ch; + ngx_uint_t i; + + if (r->schema.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :scheme header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :scheme header"); + + return NGX_DECLINED; + } + + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + continue; + } + + if (((ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.') + && i > 0) + { + continue; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid :scheme header: \"%V\"", value); + + return NGX_DECLINED; + } + + r->schema = *value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_parse_authority(ngx_http_request_t *r, ngx_str_t *value) +{ + return ngx_http_v3_parse_header(r, &ngx_http_v3_parse_headers[0], value); +} + + +static ngx_int_t +ngx_http_v3_parse_header(ngx_http_request_t *r, + ngx_http_v3_parse_header_t *header, ngx_str_t *value) +{ + ngx_table_elt_t *h; + ngx_http_core_main_conf_t *cmcf; + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key.len = header->name.len; + h->key.data = header->name.data; + h->lowcase_key = header->name.data; + + if (header->hh == NULL) { + header->hash = ngx_hash_key(header->name.data, header->name.len); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + header->hh = ngx_hash_find(&cmcf->headers_in_hash, header->hash, + h->lowcase_key, h->key.len); + if (header->hh == NULL) { + return NGX_ERROR; + } + } + + h->hash = header->hash; + + h->value.len = value->len; + h->value.data = value->data; + + if (header->hh->handler(r, h, header->hh->offset) != NGX_OK) { + /* header handler has already finalized request */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_construct_request_line(ngx_http_request_t *r) +{ + u_char *p; + + static const u_char ending[] = " HTTP/3"; + + if (r->method_name.len == 0 + || r->schema.len == 0 + || r->unparsed_uri.len == 0) + { + if (r->method_name.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :method header"); + + } else if (r->schema.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :scheme header"); + + } else { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :path header"); + } + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + r->request_line.len = r->method_name.len + 1 + + r->unparsed_uri.len + + sizeof(ending) - 1; + + p = ngx_pnalloc(r->pool, r->request_line.len + 1); + if (p == NULL) { + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + + *p++ = ' '; + + p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len); + + ngx_memcpy(p, ending, sizeof(ending)); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request line: \"%V\"", &r->request_line); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_cookie(ngx_http_request_t *r, ngx_http_v3_header_t *header) +{ + ngx_str_t *val; + ngx_array_t *cookies; + + cookies = r->qstream->cookies; + + if (cookies == NULL) { + cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t)); + if (cookies == NULL) { + return NGX_ERROR; + } + + r->qstream->cookies = cookies; + } + + val = ngx_array_push(cookies); + if (val == NULL) { + return NGX_ERROR; + } + + val->len = header->value.len; + val->data = header->value.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_construct_cookie_header(ngx_http_request_t *r) +{ + u_char *buf, *p, *end; + size_t len; + ngx_str_t *vals; + ngx_uint_t i; + ngx_array_t *cookies; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + cookies = r->qstream->cookies; + + if (cookies == NULL) { + return NGX_OK; + } + + vals = cookies->elts; + + i = 0; + len = 0; + + do { + len += vals[i].len + 2; + } while (++i != cookies->nelts); + + len -= 2; + + buf = ngx_pnalloc(r->pool, len + 1); + if (buf == NULL) { + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + p = buf; + end = buf + len; + + for (i = 0; /* void */ ; i++) { + + p = ngx_cpymem(p, vals[i].data, vals[i].len); + + if (p == end) { + *p = '\0'; + break; + } + + *p++ = ';'; *p++ = ' '; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( + ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e'); + + h->key.len = cookie.len; + h->key.data = cookie.data; + + h->value.len = len; + h->value.data = buf; + + h->lowcase_key = cookie.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_multi_header_lines() + */ + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_http_v3_run_request(ngx_http_request_t *r) +{ + if (ngx_http_v3_construct_request_line(r) != NGX_OK) { + return; + } + + if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { + return; + } + + r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; + + if (ngx_http_process_request_header(r) != NGX_OK) { + return; + } + + if (r->headers_in.content_length_n > 0 && r->qstream->in_closed) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + + r->qstream->skip_data = 1; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return; + } + + if (r->headers_in.content_length_n == -1 && !r->qstream->in_closed) { + r->headers_in.chunked = 1; + } + + ngx_http_process_request(r); +} + + +/* End of functions copied from HTTP/2 module. */ + + +ngx_int_t +ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + size_t size; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t *cl, *tl, *out, **ll; + ngx_connection_t *c; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + c = r->qstream->connection->connection; + + rb = r->request_body; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (rb->rest == -1) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request body filter"); + + if (r->headers_in.chunked) { + rb->rest = clcf->client_body_buffer_size; + r->headers_in.content_length_n = 0; + } else { + rb->rest = r->headers_in.content_length_n; + } + } + + out = NULL; + ll = &out; + + for (cl = in; cl; cl = cl->next) { + + if (rb->rest == 0) { + break; + } + + if (ngx_buf_size(cl->buf) == 0) { + continue; + } + + tl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (tl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b = tl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->temporary = 1; + b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body; + b->start = cl->buf->pos; + b->pos = cl->buf->pos; + b->last = cl->buf->last; + b->end = cl->buf->end; + b->flush = r->request_body_no_buffering; + + size = cl->buf->last - cl->buf->pos; + + cl->buf->pos = cl->buf->last; + + if (r->headers_in.chunked) { + r->headers_in.content_length_n += size; + } + + if (quiche_conn_stream_finished(c->quic->conn, r->qstream->id)) { + rb->rest = 0; + b->last = cl->buf->pos; + b->last_buf = 1; + } + + *ll = tl; + ll = &tl->next; + } + + rc = ngx_http_top_request_body_filter(r, out); + + ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out, + (ngx_buf_tag_t) &ngx_http_read_client_request_body); + + return rc; +} + + +size_t +ngx_http_v3_get_headers_out_count(ngx_http_request_t *r) +{ + size_t headers_count; + ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *header; + + headers_count = 1; /* :status */ + + if (r->headers_out.server == NULL) { + headers_count += 1; + } + + if (r->headers_out.date == NULL) { + headers_count += 1; + } + + if (r->headers_out.content_type.len) { + headers_count += 1; + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + headers_count += 1; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + headers_count += 1; + } + + if (r->headers_out.location && r->headers_out.location->value.len) { + headers_count += 1; + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + headers_count += 1; + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + headers_count += 1; + } + + return headers_count; +} + + +ngx_int_t +ngx_http_v3_push_response_headers(ngx_http_request_t *r) +{ + u_char *tmp; + size_t len, headers_count; + ngx_str_t host, location; + ngx_uint_t i, port; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_connection_t *fc; + quiche_h3_header *h; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + u_char addr[NGX_SOCKADDR_STRLEN]; + + /* The list of response headers was already generated, so there's nothing + * more to do here. */ + if (r->qstream->headers != NULL) { + return NGX_OK; + } + + fc = r->connection; + + if (r->method == NGX_HTTP_HEAD) { + r->header_only = 1; + } + + switch (r->headers_out.status) { + + case NGX_HTTP_OK: + break; + + case NGX_HTTP_NO_CONTENT: + r->header_only = 1; + + ngx_str_null(&r->headers_out.content_type); + + r->headers_out.content_length = NULL; + r->headers_out.content_length_n = -1; + + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + break; + + case NGX_HTTP_PARTIAL_CONTENT: + break; + + case NGX_HTTP_NOT_MODIFIED: + r->header_only = 1; + break; + + default: + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + } + + headers_count = ngx_http_v3_get_headers_out_count(r); + + r->qstream->headers = + ngx_array_create(r->pool, headers_count, sizeof(quiche_h3_header)); + + if (r->qstream->headers == NULL) { + return NGX_ERROR; + } + + /* Generate :status pseudo-header. */ + { + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) ":status"; + h->name_len = sizeof(":status") - 1; + + tmp = ngx_pnalloc(r->pool, sizeof("418") - 1); + if (tmp == NULL) { + return NGX_ERROR; + } + + h->value = tmp; + h->value_len = ngx_sprintf(tmp, "%03ui", r->headers_out.status) - tmp; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + /* Generate Server header.*/ + if (r->headers_out.server == NULL) { + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "server"; + h->name_len = sizeof("server") - 1; + + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + h->value = (u_char *) NGINX_VER; + h->value_len = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + h->value = (u_char *) NGINX_VER_BUILD; + h->value_len = sizeof(NGINX_VER_BUILD) - 1; + + } else { + h->value = (u_char *) "nginx"; + h->value_len = sizeof("nginx") - 1; + } + } + + /* Generate Date header. */ + if (r->headers_out.date == NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"date: %V\"", + &ngx_cached_http_time); + + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "date"; + h->name_len = sizeof("date") - 1; + + h->value = ngx_cached_http_time.data; + h->value_len = ngx_cached_http_time.len; + } + + /* Generate Content-Type header. */ + if (r->headers_out.content_type.len) { + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + len = r->headers_out.content_type.len + sizeof("; charset=") - 1 + + r->headers_out.charset.len; + + tmp = ngx_pnalloc(r->pool, len); + if (tmp == NULL) { + return NGX_ERROR; + } + + tmp = ngx_cpymem(tmp, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + tmp = ngx_cpymem(tmp, "; charset=", sizeof("; charset=") - 1); + + tmp = ngx_cpymem(tmp, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* updated r->headers_out.content_type is also needed for logging */ + + r->headers_out.content_type.len = len; + r->headers_out.content_type.data = tmp - len; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"content-type: %V\"", + &r->headers_out.content_type); + + h->name = (u_char *) "content-type"; + h->name_len = sizeof("content-type") - 1; + + h->value = r->headers_out.content_type.data; + h->value_len = r->headers_out.content_type.len; + } + + /* Generate Content-Length header. */ + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "content-length"; + h->name_len = sizeof("content-length") - 1; + + tmp = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); + if (tmp == NULL) { + return NGX_ERROR; + } + + h->value = tmp; + h->value_len = + ngx_sprintf(tmp, "%O", r->headers_out.content_length_n) - tmp; + } + + /* Generate Last-Modified header. */ + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "last-modified"; + h->name_len = sizeof("last-modified") - 1; + + tmp = ngx_pnalloc(r->pool, sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1); + if (tmp == NULL) { + return NGX_ERROR; + } + + h->value = tmp; + h->value_len = + ngx_http_time(tmp, r->headers_out.last_modified_time) - tmp; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"last-modified: %*.s\"", + h->value_len, h->value); + } + + /* Generate Location header. */ + if (r->headers_out.location && r->headers_out.location->value.len) { + + if (r->headers_out.location->value.data[0] == '/' + && clcf->absolute_redirect) + { + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.data = addr; + host.len = NGX_SOCKADDR_STRLEN; + + if (ngx_connection_local_sockaddr(fc, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + port = ngx_inet_get_port(fc->local_sockaddr); + + location.len = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + +#if (NGX_HTTP_SSL) + if (fc->ssl) + port = (port == 443) ? 0 : port; + else +#endif + port = (port == 80) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + location.len += sizeof(":65535") - 1; + } + + location.data = ngx_pnalloc(r->pool, location.len); + if (location.data == NULL) { + return NGX_ERROR; + } + + tmp = ngx_cpymem(location.data, "http", sizeof("http") - 1); + +#if (NGX_HTTP_SSL) + if (fc->ssl) { + *tmp++ = 's'; + } +#endif + + *tmp++ = ':'; *tmp++ = '/'; *tmp++ = '/'; + tmp = ngx_cpymem(tmp, host.data, host.len); + + if (port) { + tmp = ngx_sprintf(tmp, ":%ui", port); + } + + tmp = ngx_cpymem(tmp, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = tmp - location.data; + r->headers_out.location->value.data = location.data; + ngx_str_set(&r->headers_out.location->key, "Location"); + } + + r->headers_out.location->hash = 0; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"location: %V\"", + &r->headers_out.location->value); + + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "location"; + h->name_len = sizeof("location") - 1; + + h->value = r->headers_out.location->value.data; + h->value_len = r->headers_out.location->value.len; + } + +#if (NGX_HTTP_GZIP) + /* Generate Vary header. */ + if (r->gzip_vary) { + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"vary: Accept-Encoding\""); + + h->name = (u_char *) "vary"; + h->name_len = sizeof("vary") - 1; + + h->value = (u_char *) "Accept-Encoding"; + h->value_len = sizeof("Accept-Encoding") - 1; + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + /* Generate all other headers. */ + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + +#if (NGX_DEBUG) + if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"%V: %V\"", + &header[i].key, &header[i].value); + } +#endif + + h->name = header[i].key.data; + h->name_len = header[i].key.len; + + h->value = header[i].value.data; + h->value_len = header[i].value.len; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_send_response(ngx_http_request_t *r) +{ + int rc; + ngx_uint_t fin; + ngx_connection_t *c, *fc; + ngx_http_v3_connection_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 send response stream %ui", r->qstream->id); + + fc = r->connection; + + if (fc->error) { + return NGX_ERROR; + } + + h3c = r->qstream->connection; + c = h3c->connection; + + if (ngx_http_v3_push_response_headers(r) != NGX_OK) { + return NGX_ERROR; + } + + fin = r->header_only + || (r->headers_out.content_length_n == 0 && !r->expect_trailers); + + rc = quiche_h3_send_response(h3c->h3, c->quic->conn, r->qstream->id, + r->qstream->headers->elts, + r->qstream->headers->nelts, + fin); + + if (rc == QUICHE_H3_ERR_STREAM_BLOCKED) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 stream blocked %ui", r->qstream->id); + + r->qstream->blocked = 1; + + fc->write->active = 1; + fc->write->ready = 0; + + return NGX_AGAIN; + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + + if (fin) { + r->qstream->out_closed = 1; + } + + r->qstream->headers_sent = 1; + + if (r->done) { + fc->write->handler = ngx_http_v3_close_stream_handler; + fc->read->handler = ngx_http_empty_handler; + } + + ngx_post_event(c->write, &ngx_posted_events); + + return NGX_OK; +} + + +static ssize_t +ngx_http_v3_stream_do_send(ngx_connection_t *fc, ngx_buf_t *b, ngx_int_t fin) +{ + ssize_t n; + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_v3_connection_t *h3c; + ngx_http_v3_stream_t *stream; + + uint8_t *buf = b ? b->pos : NULL; + size_t buf_len = b ? ngx_buf_size(b) : 0; + + r = fc->data; + stream = r->qstream; + h3c = stream->connection; + c = h3c->connection; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, fc->log, 0, + "http3 stream %uz to write %uz bytes, fin=%d", + stream->id, buf_len, fin); + + if (!stream->headers_sent) { + return NGX_AGAIN; + } + + n = quiche_h3_send_body(h3c->h3, c->quic->conn, r->qstream->id, + buf, buf_len, fin); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, stream->connection->connection->log, 0, + "http3 stream written %z bytes", n); + + if (n == QUICHE_H3_ERR_DONE) { + return NGX_AGAIN; + } + + if (n < 0) { + ngx_log_error(NGX_LOG_ERR, fc->log, 0, "stream write failed: %d", n); + return NGX_ERROR; + } + + return n; +} + + +static ssize_t +ngx_http_v3_recv_body(ngx_connection_t *c, u_char *buf, size_t size) +{ + ssize_t n; + ngx_event_t *rev; + ngx_http_request_t *r; + ngx_http_v3_connection_t *h3c; + + rev = c->read; + + r = c->data; + h3c = r->qstream->connection; + + if (c->error) { + rev->ready = 0; + + return NGX_ERROR; + } + + n = quiche_h3_recv_body(h3c->h3, c->quic->conn, r->qstream->id, buf, size); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "http3 body recv: %z of %uz", n, size); + + if (quiche_conn_stream_finished(c->quic->conn, r->qstream->id)) { + rev->ready = 0; + + /* Re-schedule connection read event to poll for Finished event. */ + ngx_post_event(h3c->connection->read, &ngx_posted_events); + } + + if (n == 0) { + rev->ready = 0; + + return 0; + } + + if (n > 0) { + + if ((size_t) n < size) { + rev->ready = 0; + } + + return n; + } + + if (n == QUICHE_H3_ERR_DONE) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quiche_h3_recv_body() not ready"); + + n = NGX_AGAIN; + + } else { + rev->error = 1; + + n = NGX_ERROR; + } + + rev->ready = 0; + + return n; +} + + +static ngx_chain_t * +ngx_http_v3_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) +{ + ssize_t n, sent; + off_t send, prev_send; + ngx_uint_t blocked, fin; + + ngx_http_request_t *r; + ngx_http_v3_stream_t *stream; + + r = fc->data; + stream = r->qstream; + + send = 0; + + blocked = 0; + + while (in) { + off_t size = ngx_buf_size(in->buf); + + if (size || in->buf->last_buf) { + break; + } + + in = in->next; + } + + if (in == NULL || stream->out_closed) { + return NULL; + } + + while (in) { + prev_send = send; + + fin = in->buf->last_buf; + + send += ngx_buf_size(in->buf); + + n = ngx_http_v3_stream_do_send(fc, in->buf, fin); + + if (n == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + sent = (n == NGX_AGAIN) ? 0 : n; + + fc->sent += sent; + + in->buf->pos += sent; + + /* Partial (or no) write, end now. */ + if ((n == NGX_AGAIN) || (send - prev_send != sent)) { + blocked = 1; + break; + } + + /* Buffer is fully written, switch to the next. */ + if (in->buf->pos == in->buf->last) { + in = in->next; + } + + if (fin) { + stream->out_closed = 1; + } + } + + if (blocked) { + if (!stream->blocked) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, stream->connection->connection->log, 0, + "http3 stream blocked %ui", stream->id); + + stream->blocked = 1; + + fc->write->active = 1; + fc->write->ready = 0; + } + } + + ngx_post_event(stream->connection->connection->write, &ngx_posted_events); + + return in; +} + + +void +ngx_http_v3_close_stream(ngx_http_v3_stream_t *stream, ngx_int_t rc) +{ + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_v3_connection_t *h3c; + + h3c = stream->connection; + + fc = stream->request->connection; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 close stream %ui", stream->id); + + if (stream->blocked) { + fc->write->handler = ngx_http_v3_close_stream_handler; + fc->read->handler = ngx_http_empty_handler; + return; + } + + quiche_conn_stream_shutdown(h3c->connection->quic->conn, stream->id, + QUICHE_SHUTDOWN_READ, 0); + + ngx_rbtree_delete(&h3c->streams, &stream->node); + + ngx_http_free_request(stream->request, rc); + + ev = fc->read; + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + ev = fc->write; + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + fc->data = h3c->free_fake_connections; + h3c->free_fake_connections = fc; + + h3c->processing--; + + ngx_http_v3_handle_connection(h3c); +} + + +static void +ngx_http_v3_close_stream_handler(ngx_event_t *ev) +{ + ngx_connection_t *fc; + ngx_http_request_t *r; + + fc = ev->data; + r = fc->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 close stream handler"); + + if (ev->timedout) { + ngx_log_error(NGX_LOG_INFO, fc->log, NGX_ETIMEDOUT, "client timed out"); + + fc->timedout = 1; + + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + ngx_http_v3_close_stream(r->qstream, 0); +} + +void +ngx_http_v3_stop_stream_read(ngx_http_v3_stream_t *stream, ngx_int_t rc) +{ + ngx_http_v3_connection_t *h3c; + + if (!stream) { + return; + } + + h3c = stream->connection; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 stream shutdown read %ui", stream->id); + + quiche_conn_stream_shutdown(h3c->connection->quic->conn, + stream->id, + QUICHE_SHUTDOWN_READ, rc); +} + + +static void +ngx_http_v3_finalize_connection(ngx_http_v3_connection_t *h3c, + ngx_uint_t status) +{ + ngx_event_t *ev; + ngx_connection_t *c, *fc; + ngx_rbtree_node_t *node, *root, *sentinel; + ngx_http_request_t *r; + ngx_http_v3_stream_t *stream; + + c = h3c->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 finalize connection"); + + quiche_conn_close(c->quic->conn, true, status, NULL, 0); + + c->error = 1; + + if (!h3c->processing) { + ngx_http_close_connection(c); + return; + } + + c->read->handler = ngx_http_empty_handler; + c->write->handler = ngx_http_empty_handler; + + root = h3c->streams.root; + sentinel = h3c->streams.sentinel; + + if (root != sentinel) { + node = ngx_rbtree_min(h3c->streams.root, sentinel); + } else { + node = NULL; + } + + /* Close all pending streams / requests. */ + while (node != NULL) { + stream = (ngx_http_v3_stream_t *) node; + + r = stream->request; + fc = r->connection; + + fc->error = 1; + + if (c->close) { + fc->close = 1; + } + + if (stream->blocked) { + stream->blocked = 0; + + ev = fc->write; + ev->active = 0; + ev->ready = 1; + + } else { + ev = fc->read; + } + + node = ngx_rbtree_next(&h3c->streams, node); + + ev->eof = 1; + ev->handler(ev); + } + + if (h3c->processing) { + return; + } + + ngx_http_close_connection(c); +} + + +static void +ngx_http_v3_pool_cleanup(void *data) +{ + ngx_http_v3_connection_t *h3c = data; + + if (h3c->h3) { + quiche_h3_conn_free(h3c->h3); + + h3c->h3 = NULL; + } +} diff --color -uNr a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c --- a/src/http/v3/ngx_http_v3_filter_module.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3_filter_module.c 2023-03-12 11:07:31.424412108 +0800 @@ -0,0 +1,68 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#include +#include +#include +#include + + +static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_v3_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_v3_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_filter_module = { + NGX_MODULE_V1, + &ngx_http_v3_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; + + +static ngx_int_t +ngx_http_v3_header_filter(ngx_http_request_t *r) +{ + if (!r->qstream) { + return ngx_http_next_header_filter(r); + } + + return ngx_http_v3_send_response(r); +} + + +static ngx_int_t +ngx_http_v3_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_v3_header_filter; + + return NGX_OK; +} diff --color -uNr a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h --- a/src/http/v3/ngx_http_v3.h 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3.h 2023-03-12 11:07:31.424412108 +0800 @@ -0,0 +1,79 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#ifndef _NGX_HTTP_V3_H_INCLUDED_ +#define _NGX_HTTP_V3_H_INCLUDED_ + + +#include +#include +#include +#include + + +#define NGX_HTTP_V3_ALPN_ADVERTISE "\x05h3-18" + + +typedef struct ngx_http_v3_connection_s ngx_http_v3_connection_t; + + +struct ngx_http_v3_connection_s { + quiche_h3_conn *h3; + + ngx_connection_t *connection; + ngx_http_connection_t *http_connection; + + ngx_pool_t *pool; + + ngx_uint_t processing; + + ngx_rbtree_t streams; + ngx_rbtree_node_t streams_sentinel; + + ngx_connection_t *free_fake_connections; +}; + + +struct ngx_http_v3_stream_s { + ngx_rbtree_node_t node; + + uint64_t id; + + ngx_http_request_t *request; + + ngx_http_v3_connection_t *connection; + + ngx_array_t *headers; + ngx_array_t *cookies; + + ngx_http_v3_stream_t *next; + + ngx_uint_t headers_sent:1; + ngx_uint_t in_closed:1; + ngx_uint_t out_closed:1; + ngx_uint_t skip_data:1; + ngx_uint_t blocked:1; +}; + + +typedef struct { + ngx_str_t name; + ngx_str_t value; +} ngx_http_v3_header_t; + + +void ngx_http_v3_init(ngx_event_t *rev); + +ngx_int_t ngx_http_v3_send_response(ngx_http_request_t *r); + +void ngx_http_v3_close_stream(ngx_http_v3_stream_t *stream, ngx_int_t rc); +void ngx_http_v3_stop_stream_read(ngx_http_v3_stream_t *stream, ngx_int_t rc); + +ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r, + ngx_chain_t *in); + + +#endif /* _NGX_HTTP_V3_H_INCLUDED_ */ diff --color -uNr a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c --- a/src/http/v3/ngx_http_v3_module.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3_module.c 2023-03-12 11:07:31.424412108 +0800 @@ -0,0 +1,286 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#include +#include +#include +#include + +#include + + +static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); + +static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, + void *parent, void *child); + +static ngx_int_t ngx_http_v3_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static void ngx_http_v3_cleanup_ctx(void *data); + + +static ngx_command_t ngx_http_v3_commands[] = { + + { ngx_string("http3_max_concurrent_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, concurrent_streams), + NULL }, + + { ngx_string("http3_max_requests"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_requests), + NULL }, + + { ngx_string("http3_max_header_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_header_size), + NULL }, + + { ngx_string("http3_initial_max_data"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_data), + NULL }, + + { ngx_string("http3_initial_max_stream_data"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_stream_data), + NULL }, + + { ngx_string("http3_idle_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, idle_timeout), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_v3_module_ctx = { + ngx_http_v3_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_http_v3_create_srv_conf, /* create server configuration */ + ngx_http_v3_merge_srv_conf, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_module = { + NGX_MODULE_V1, + &ngx_http_v3_module_ctx, /* module context */ + ngx_http_v3_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_variable_t ngx_http_v3_variables[] = { + + { ngx_string("http3"), NULL, + ngx_http_v3_variable, 0, + NGX_HTTP_VAR_CHANGEABLE, 0 }, + + ngx_http_null_variable +}; + + +static ngx_int_t +ngx_http_v3_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_v3_variables; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static void * +ngx_http_v3_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_v3_srv_conf_t *h3scf; + + h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t)); + if (h3scf == NULL) { + return NULL; + } + + h3scf->idle_timeout = NGX_CONF_UNSET_MSEC; + h3scf->max_data = NGX_CONF_UNSET_SIZE; + h3scf->max_stream_data = NGX_CONF_UNSET_SIZE; + h3scf->max_requests = NGX_CONF_UNSET_UINT; + h3scf->max_header_size = NGX_CONF_UNSET_SIZE; + h3scf->concurrent_streams = NGX_CONF_UNSET_UINT; + + return h3scf; +} + + +#if (NGX_DEBUG) +static void +quiche_log(const char *line, void *argp) +{ + ngx_log_t *log = ngx_cycle->log; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "%s", line); +} +#endif + + +static char * +ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_v3_srv_conf_t *prev = parent; + ngx_http_v3_srv_conf_t *conf = child; + + ngx_pool_cleanup_t *cln; + + ngx_conf_merge_msec_value(conf->idle_timeout, + prev->idle_timeout, 180000); + + ngx_conf_merge_size_value(conf->max_data, + prev->max_data, 10485760); + + ngx_conf_merge_size_value(conf->max_stream_data, + prev->max_stream_data, 1048576); + + ngx_conf_merge_uint_value(conf->max_requests, + prev->max_requests, 1000); + + ngx_conf_merge_size_value(conf->max_header_size, + prev->max_header_size, 16384); + + ngx_conf_merge_uint_value(conf->concurrent_streams, + prev->concurrent_streams, 128); + + conf->quic.log = cf->log; + +#if (NGX_DEBUG) + /* Enable quiche debug logging. quiche commit ceade4 or later is required */ + quiche_enable_debug_logging(quiche_log, NULL); +#endif + + if (ngx_quic_create_conf(&conf->quic) != NGX_OK) { + return NGX_CONF_ERROR; + } + + quiche_config_set_max_idle_timeout(conf->quic.config, conf->idle_timeout); + + quiche_config_set_initial_max_data(conf->quic.config, conf->max_data); + + quiche_config_set_initial_max_stream_data_bidi_remote(conf->quic.config, + conf->max_stream_data); + + quiche_config_set_initial_max_stream_data_uni(conf->quic.config, + conf->max_stream_data); + + quiche_config_set_initial_max_streams_bidi(conf->quic.config, + conf->concurrent_streams); + + /* For HTTP/3 we only need 3 unidirectional streams. */ + quiche_config_set_initial_max_streams_uni(conf->quic.config, 3); + + conf->http3 = quiche_h3_config_new(); + if (conf->http3 == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "failed to create HTTP/3 config"); + return NGX_CONF_ERROR; + } + + quiche_h3_config_set_max_field_section_size(conf->http3, + conf->max_header_size); + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NGX_CONF_ERROR; + } + + cln->handler = ngx_quic_cleanup_ctx; + cln->data = &conf->quic; + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NGX_CONF_ERROR; + } + + cln->handler = ngx_http_v3_cleanup_ctx; + cln->data = conf->http3; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_v3_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, + uintptr_t data) +{ + ngx_connection_t *c; + + v->valid = 1; + v->no_cacheable = 1; + v->not_found = 0; + + c = r->connection; + if (c == NULL) { + return NGX_ERROR; + } + + if (c->quic != NULL) { + v->len = sizeof("h3") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "h3"; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + return NGX_OK; +} + + +static void +ngx_http_v3_cleanup_ctx(void *data) +{ + quiche_h3_config *config = data; + + quiche_h3_config_free(config); +} diff --color -uNr a/src/http/v3/ngx_http_v3_module.h b/src/http/v3/ngx_http_v3_module.h --- a/src/http/v3/ngx_http_v3_module.h 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3_module.h 2023-03-12 11:07:31.424412108 +0800 @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#ifndef _NGX_HTTP_V3_MODULE_H_INCLUDED_ +#define _NGX_HTTP_V3_MODULE_H_INCLUDED_ + + +#include +#include + +#include + + +typedef struct { + ngx_quic_t quic; + + quiche_h3_config *http3; + + ngx_msec_t idle_timeout; + size_t max_data; + size_t max_stream_data; + ngx_uint_t max_requests; + ngx_uint_t max_header_size; + ngx_uint_t concurrent_streams; +} ngx_http_v3_srv_conf_t; + + +extern ngx_module_t ngx_http_v3_module; + + +#endif /* _NGX_HTTP_V3_MODULE_H_INCLUDED_ */ diff --color -uNr a/src/os/unix/ngx_udp_sendmsg_chain.c b/src/os/unix/ngx_udp_sendmsg_chain.c --- a/src/os/unix/ngx_udp_sendmsg_chain.c 2022-12-13 23:53:53.000000000 +0800 +++ b/src/os/unix/ngx_udp_sendmsg_chain.c 2023-03-12 11:07:31.424412108 +0800 @@ -403,6 +403,7 @@ switch (err) { case NGX_EAGAIN: + case ENOBUFS: ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, "sendmsg() not ready"); return NGX_AGAIN; ================================================ FILE: nginx_with_quic_for_1.19.6.patch ================================================ diff --color -uNr a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c 2021-04-09 18:22:29.419033226 +0800 +++ b/src/http/ngx_http_request.c 2021-04-09 18:24:32.080095491 +0800 @@ -361,8 +361,7 @@ /* We already have a UDP packet in the connection buffer, so we don't * need to wait for another read event to kick-off the handshake. */ - cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); - ngx_add_timer(rev, cscf->client_header_timeout); + ngx_add_timer(rev, c->listening->post_accept_timeout); ngx_http_quic_handshake(rev); return; } @@ -1103,7 +1102,6 @@ ngx_http_connection_t *hc; ngx_http_v3_srv_conf_t *qscf; ngx_http_ssl_srv_conf_t *sscf; - ngx_http_core_srv_conf_t *cscf; c = rev->data; hc = c->data; @@ -1144,8 +1142,7 @@ if (rc == NGX_AGAIN) { if (!rev->timer_set) { - cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); - ngx_add_timer(rev, cscf->client_header_timeout); + ngx_add_timer(rev, c->listening->post_accept_timeout); } c->ssl->handler = ngx_http_ssl_handshake_handler; ================================================ FILE: nginx_with_quic_for_1.19.7_full.patch ================================================ Add HTTP3(QUIC) Support. Add HTTP2 HPACK Encoding Support. Add Dynamic TLS Record support. Using: patch -p1 < nginx_with_quic_for_1.19.7_full.patch Based on cloudflare/quiche af1bbc0. Requires nginx 1.19.7 ~ 1.21.3 to patch. diff --color -uNr a/auto/lib/conf b/auto/lib/conf --- a/auto/lib/conf 2021-09-07 23:21:03.000000000 +0800 +++ b/auto/lib/conf 2021-09-09 19:04:29.388062635 +0800 @@ -25,6 +25,10 @@ . auto/lib/openssl/conf fi +if [ $USE_QUICHE = YES ]; then + . auto/lib/quiche/conf +fi + if [ $USE_ZLIB = YES ]; then . auto/lib/zlib/conf fi diff --color -uNr a/auto/lib/make b/auto/lib/make --- a/auto/lib/make 2021-09-07 23:21:03.000000000 +0800 +++ b/auto/lib/make 2021-09-09 19:04:29.388062635 +0800 @@ -11,6 +11,10 @@ . auto/lib/openssl/make fi +if [ $QUICHE != NONE -a $QUICHE != NO -a $QUICHE != YES ]; then + . auto/lib/quiche/make +fi + if [ $ZLIB != NONE -a $ZLIB != NO -a $ZLIB != YES ]; then . auto/lib/zlib/make fi diff --color -uNr a/auto/lib/openssl/make b/auto/lib/openssl/make --- a/auto/lib/openssl/make 2021-09-07 23:21:03.000000000 +0800 +++ b/auto/lib/openssl/make 2021-09-09 19:04:29.388062635 +0800 @@ -49,11 +49,13 @@ cat << END >> $NGX_MAKEFILE $OPENSSL/.openssl/include/openssl/ssl.h: $NGX_MAKEFILE - cd $OPENSSL \\ - && if [ -f Makefile ]; then \$(MAKE) clean; fi \\ - && ./config --prefix=$ngx_prefix no-shared no-threads $OPENSSL_OPT \\ - && \$(MAKE) \\ - && \$(MAKE) install_sw LIBDIR=lib + mkdir -p $OPENSSL/build $OPENSSL/.openssl/lib $OPENSSL/.openssl/include/openssl \\ + && cd $OPENSSL/build \\ + && cmake -DCMAKE_C_FLAGS="$OPENSSL_OPT" -DCMAKE_CXX_FLAGS="$OPENSSL_OPT" .. \\ + && \$(MAKE) VERBOSE=1 \\ + && cd .. \\ + && cp -r src/include/openssl/*.h .openssl/include/openssl \\ + && cp build/libssl.a build/libcrypto.a .openssl/lib END diff --color -uNr a/auto/lib/quiche/conf b/auto/lib/quiche/conf --- a/auto/lib/quiche/conf 1970-01-01 08:00:00.000000000 +0800 +++ b/auto/lib/quiche/conf 2021-09-09 19:04:29.388062635 +0800 @@ -0,0 +1,23 @@ + +# Copyright (C) Cloudflare, Inc. + + +if [ $QUICHE != NONE ]; then + + have=NGX_QUIC . auto/have + + QUICHE_BUILD_TARGET="release" + + if [ $NGX_DEBUG = YES ]; then + QUICHE_BUILD_TARGET="debug" + fi + + CORE_INCS="$CORE_INCS $QUICHE/include" + CORE_DEPS="$CORE_DEPS $QUICHE/target/$QUICHE_BUILD_TARGET/libquiche.a" + CORE_LIBS="$CORE_LIBS $QUICHE/target/$QUICHE_BUILD_TARGET/libquiche.a $NGX_LIBPTHREAD -lm" + + if [ "$NGX_SYSTEM" = "Darwin" ]; then + CORE_LIBS+=" -framework Security" + fi + +fi diff --color -uNr a/auto/lib/quiche/make b/auto/lib/quiche/make --- a/auto/lib/quiche/make 1970-01-01 08:00:00.000000000 +0800 +++ b/auto/lib/quiche/make 2021-09-09 19:04:29.389062662 +0800 @@ -0,0 +1,23 @@ + +# Copyright (C) Cloudflare, Inc. + +QUICHE_COMMON_FLAGS="--verbose --no-default-features --features ffi" + +# Default is release build +QUICHE_BUILD_FLAGS="$QUICHE_COMMON_FLAGS --release" +QUICHE_BUILD_TARGET="release" + +if [ $NGX_DEBUG = YES ]; then + QUICHE_BUILD_FLAGS="$QUICHE_COMMON_FLAGS" + QUICHE_BUILD_TARGET="debug" +fi + + +cat << END >> $NGX_MAKEFILE + +$QUICHE/target/$QUICHE_BUILD_TARGET/libquiche.a: \\ + $OPENSSL/.openssl/include/openssl/ssl.h \\ + $NGX_MAKEFILE + cd $QUICHE && cargo build $QUICHE_BUILD_FLAGS $QUICHE_OPT + +END diff --color -uNr a/auto/make b/auto/make --- a/auto/make 2021-09-07 23:21:03.000000000 +0800 +++ b/auto/make 2021-09-09 19:04:29.389062662 +0800 @@ -7,7 +7,8 @@ mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \ $NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \ - $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \ + $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \ + $NGX_OBJS/src/http/modules \ $NGX_OBJS/src/http/modules/perl \ $NGX_OBJS/src/mail \ $NGX_OBJS/src/stream \ diff --color -uNr a/auto/modules b/auto/modules --- a/auto/modules 2021-09-07 23:21:03.000000000 +0800 +++ b/auto/modules 2021-09-09 19:04:41.601382488 +0800 @@ -119,6 +119,7 @@ # ngx_http_header_filter # ngx_http_chunked_filter # ngx_http_v2_filter + # ngx_http_v3_filter # ngx_http_range_header_filter # ngx_http_gzip_filter # ngx_http_postpone_filter @@ -151,6 +152,7 @@ ngx_http_header_filter_module \ ngx_http_chunked_filter_module \ ngx_http_v2_filter_module \ + ngx_http_v3_filter_module \ ngx_http_range_header_filter_module \ ngx_http_gzip_filter_module \ ngx_http_postpone_filter_module \ @@ -212,6 +214,17 @@ . auto/module fi + if [ $HTTP_V3 = YES ]; then + ngx_module_name=ngx_http_v3_filter_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs=src/http/v3/ngx_http_v3_filter_module.c + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + if :; then ngx_module_name=ngx_http_range_header_filter_module ngx_module_incs= @@ -423,6 +436,28 @@ . auto/module fi + if [ $HTTP_V3 = YES ]; then + USE_QUICHE=YES + USE_OPENSSL=YES + have=NGX_HTTP_V3 . auto/have + have=NGX_HTTP_HEADERS . auto/have + + ngx_module_name=ngx_http_v3_module + ngx_module_incs=src/http/v3 + ngx_module_deps="src/http/v3/ngx_http_v3.h \ + src/http/v3/ngx_http_v3_module.h" + ngx_module_srcs="src/http/v3/ngx_http_v3.c \ + src/http/v3/ngx_http_v3_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + + if [ $HTTP_V2_HPACK_ENC = YES ]; then + have=NGX_HTTP_V2_HPACK_ENC . auto/have + fi + if :; then ngx_module_name=ngx_http_static_module ngx_module_incs= @@ -1267,6 +1302,19 @@ . auto/module fi + + +if [ $USE_QUICHE = YES ]; then + ngx_module_type=CORE + ngx_module_name=ngx_quic_module + ngx_module_incs= + ngx_module_deps=src/event/ngx_event_quic.h + ngx_module_srcs=src/event/ngx_event_quic.c + ngx_module_libs= + ngx_module_link=YES + + . auto/module +fi if [ $USE_PCRE = YES ]; then diff --color -uNr a/auto/options b/auto/options --- a/auto/options 2021-09-07 23:21:03.000000000 +0800 +++ b/auto/options 2021-09-09 19:04:41.604382567 +0800 @@ -59,6 +59,8 @@ HTTP_GZIP=YES HTTP_SSL=NO HTTP_V2=NO +HTTP_V2_HPACK_ENC=NO +HTTP_V3=NO HTTP_SSI=YES HTTP_REALIP=NO HTTP_XSLT=NO @@ -150,6 +152,9 @@ USE_OPENSSL=NO OPENSSL=NONE +USE_QUICHE=NO +QUICHE=NONE + USE_ZLIB=NO ZLIB=NONE ZLIB_OPT= @@ -227,6 +232,8 @@ --with-http_ssl_module) HTTP_SSL=YES ;; --with-http_v2_module) HTTP_V2=YES ;; + --with-http_v2_hpack_enc) HTTP_V2_HPACK_ENC=YES ;; + --with-http_v3_module) HTTP_V3=YES ;; --with-http_realip_module) HTTP_REALIP=YES ;; --with-http_addition_module) HTTP_ADDITION=YES ;; --with-http_xslt_module) HTTP_XSLT=YES ;; @@ -361,6 +368,9 @@ --with-openssl=*) OPENSSL="$value" ;; --with-openssl-opt=*) OPENSSL_OPT="$value" ;; + --with-quiche=*) QUICHE="$value" ;; + --with-quiche-opt=*) QUICHE_OPT="$value" ;; + --with-md5=*) NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG $0: warning: the \"--with-md5\" option is deprecated" @@ -443,6 +453,8 @@ --with-http_ssl_module enable ngx_http_ssl_module --with-http_v2_module enable ngx_http_v2_module + --with-http_v2_hpack_enc enable ngx_http_v2_hpack_enc + --with-http_v3_module enable ngx_http_v3_module --with-http_realip_module enable ngx_http_realip_module --with-http_addition_module enable ngx_http_addition_module --with-http_xslt_module enable ngx_http_xslt_module diff --color -uNr a/src/core/ngx_connection.h b/src/core/ngx_connection.h --- a/src/core/ngx_connection.h 2021-09-07 23:21:03.000000000 +0800 +++ b/src/core/ngx_connection.h 2021-09-09 19:04:29.390062688 +0800 @@ -77,6 +77,9 @@ unsigned deferred_accept:1; unsigned delete_deferred:1; unsigned add_deferred:1; +#if (NGX_QUIC) + unsigned quic:1; +#endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) char *accept_filter; #endif @@ -153,6 +156,10 @@ ngx_udp_connection_t *udp; +#if (NGX_QUIC) + ngx_quic_connection_t *quic; +#endif + struct sockaddr *local_sockaddr; socklen_t local_socklen; diff --color -uNr a/src/core/ngx_core.h b/src/core/ngx_core.h --- a/src/core/ngx_core.h 2021-09-07 23:21:03.000000000 +0800 +++ b/src/core/ngx_core.h 2021-09-09 19:04:29.390062688 +0800 @@ -83,6 +83,9 @@ #if (NGX_OPENSSL) #include #endif +#if (NGX_QUIC) +#include +#endif #include #include #include diff --color -uNr a/src/core/ngx_murmurhash.c b/src/core/ngx_murmurhash.c --- a/src/core/ngx_murmurhash.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/core/ngx_murmurhash.c 2021-09-09 19:04:41.604382567 +0800 @@ -50,3 +50,63 @@ return h; } + + +uint64_t +ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed) +{ + uint64_t h, k; + + h = seed ^ len; + + while (len >= 8) { + k = data[0]; + k |= data[1] << 8; + k |= data[2] << 16; + k |= data[3] << 24; + k |= (uint64_t)data[4] << 32; + k |= (uint64_t)data[5] << 40; + k |= (uint64_t)data[6] << 48; + k |= (uint64_t)data[7] << 56; + + k *= 0xc6a4a7935bd1e995ull; + k ^= k >> 47; + k *= 0xc6a4a7935bd1e995ull; + + h ^= k; + h *= 0xc6a4a7935bd1e995ull; + + data += 8; + len -= 8; + } + + switch (len) { + case 7: + h ^= (uint64_t)data[6] << 48; + /* fall through */ + case 6: + h ^= (uint64_t)data[5] << 40; + /* fall through */ + case 5: + h ^= (uint64_t)data[4] << 32; + /* fall through */ + case 4: + h ^= data[3] << 24; + /* fall through */ + case 3: + h ^= data[2] << 16; + /* fall through */ + case 2: + h ^= data[1] << 8; + /* fall through */ + case 1: + h ^= data[0]; + h *= 0xc6a4a7935bd1e995ull; + } + + h ^= h >> 47; + h *= 0xc6a4a7935bd1e995ull; + h ^= h >> 47; + + return h; +} diff --color -uNr a/src/core/ngx_murmurhash.h b/src/core/ngx_murmurhash.h --- a/src/core/ngx_murmurhash.h 2021-09-07 23:21:03.000000000 +0800 +++ b/src/core/ngx_murmurhash.h 2021-09-09 19:04:41.605382593 +0800 @@ -15,5 +15,7 @@ uint32_t ngx_murmur_hash2(u_char *data, size_t len); +uint64_t ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed); + #endif /* _NGX_MURMURHASH_H_INCLUDED_ */ diff --color -uNr a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/event/ngx_event_openssl.c 2021-09-09 19:04:41.610382724 +0800 @@ -1624,6 +1624,7 @@ sc->buffer = ((flags & NGX_SSL_BUFFER) != 0); sc->buffer_size = ssl->buffer_size; + sc->dyn_rec = ssl->dyn_rec; sc->session_ctx = ssl->ctx; @@ -2569,6 +2570,41 @@ for ( ;; ) { + /* Dynamic record resizing: + We want the initial records to fit into one TCP segment + so we don't get TCP HoL blocking due to TCP Slow Start. + A connection always starts with small records, but after + a given amount of records sent, we make the records larger + to reduce header overhead. + After a connection has idled for a given timeout, begin + the process from the start. The actual parameters are + configurable. If dyn_rec_timeout is 0, we assume dyn_rec is off. */ + + if (c->ssl->dyn_rec.timeout > 0 ) { + + if (ngx_current_msec - c->ssl->dyn_rec_last_write > + c->ssl->dyn_rec.timeout) + { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + c->ssl->dyn_rec_records_sent = 0; + + } else { + if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold * 2) + { + buf->end = buf->start + c->ssl->buffer_size; + + } else if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold) + { + buf->end = buf->start + c->ssl->dyn_rec.size_hi; + + } else { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + } + } + } + while (in && buf->last < buf->end && send < limit) { if (in->buf->last_buf || in->buf->flush) { flush = 1; @@ -2676,6 +2712,9 @@ if (n > 0) { + c->ssl->dyn_rec_records_sent++; + c->ssl->dyn_rec_last_write = ngx_current_msec; + if (c->ssl->saved_read_handler) { c->read->handler = c->ssl->saved_read_handler; diff --color -uNr a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h 2021-09-07 23:21:03.000000000 +0800 +++ b/src/event/ngx_event_openssl.h 2021-09-09 19:04:41.611382750 +0800 @@ -78,10 +78,19 @@ typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; +typedef struct { + ngx_msec_t timeout; + ngx_uint_t threshold; + size_t size_lo; + size_t size_hi; +} ngx_ssl_dyn_rec_t; + + struct ngx_ssl_s { SSL_CTX *ctx; ngx_log_t *log; size_t buffer_size; + ngx_ssl_dyn_rec_t dyn_rec; }; @@ -118,6 +127,10 @@ unsigned in_ocsp:1; unsigned early_preread:1; unsigned write_blocked:1; + + ngx_ssl_dyn_rec_t dyn_rec; + ngx_msec_t dyn_rec_last_write; + ngx_uint_t dyn_rec_records_sent; }; @@ -127,7 +140,7 @@ #define NGX_SSL_DFLT_BUILTIN_SCACHE -5 -#define NGX_SSL_MAX_SESSION_SIZE 4096 +#define NGX_SSL_MAX_SESSION_SIZE 16384 typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t; diff --color -uNr a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c --- a/src/event/ngx_event_quic.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/event/ngx_event_quic.c 2021-09-09 19:04:29.391062714 +0800 @@ -0,0 +1,620 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + +#include +#include +#include + + +/* Limit outgoing packets to 1200 bytes. This is the minimum value allowed. */ +#define MAX_DATAGRAM_SIZE 1200 + +/* errors */ +#define NGX_QUIC_NO_ERROR 0x0 +#define NGX_QUIC_INTERNAL_ERROR 0x1 + + +static void ngx_quic_read_handler(ngx_event_t *ev); +static void ngx_quic_write_handler(ngx_event_t *ev); + +static void ngx_quic_set_timer(ngx_connection_t *c); + +static void ngx_quic_handshake_completed(ngx_connection_t *c); + +static void ngx_quic_shutdown_handler(ngx_event_t *ev); + +static void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t status); +static void ngx_quic_close_connection(ngx_connection_t *c); + +static ngx_int_t ngx_quic_send_udp_packet(ngx_connection_t *c, uint8_t *buf, + size_t len); + + +static ngx_command_t ngx_quic_commands[] = { + + ngx_null_command +}; + + +static ngx_core_module_t ngx_quic_module_ctx = { + ngx_string("quic"), + NULL, + NULL +}; + + +ngx_module_t ngx_quic_module = { + NGX_MODULE_V1, + &ngx_quic_module_ctx, /* module context */ + ngx_quic_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +ngx_int_t +ngx_quic_create_conf(ngx_quic_t *quic) +{ + quic->config = quiche_config_new(QUICHE_PROTOCOL_VERSION); + if (quic->config == NULL) { + ngx_log_error(NGX_LOG_EMERG, quic->log, 0, "failed to create quic config"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_validate_initial(ngx_event_t *ev, u_char *buf, ssize_t buf_len) +{ + /* Check incoming packet type, if it's not Initial we shouldn't be here. */ + if (((buf[0] & 0x30) >> 4) != 0) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "packet is not quic client initial"); + return NGX_ERROR; + } + + /* Client Initial packets must be at least 1200 bytes. */ + if (buf_len < QUICHE_MIN_CLIENT_INITIAL_LEN) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "quic initial packet is too short"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_create_connection(ngx_quic_t *quic, ngx_connection_t *c) +{ + int rc; + u_char *buf; + size_t buf_len; + quiche_conn *conn; + static uint8_t out[MAX_DATAGRAM_SIZE]; + + uint8_t pkt_type; + uint32_t pkt_version; + + uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; + size_t scid_len = sizeof(scid); + + uint8_t dcid[QUICHE_MAX_CONN_ID_LEN]; + size_t dcid_len = sizeof(dcid); + + uint8_t token[1]; + size_t token_len = sizeof(token); + + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init connection"); + + /* Extract some fields from the client's Initial packet, which was saved + * into c->buffer by ngx_event_recvmsg(). */ + buf = c->buffer->pos; + buf_len = ngx_buf_size(c->buffer); + + rc = quiche_header_info(buf, buf_len, QUICHE_MAX_CONN_ID_LEN, + &pkt_version, &pkt_type, + scid, &scid_len, dcid, &dcid_len, + token, &token_len); + if (rc < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to parse quic header: %d", rc); + return NGX_ERROR; + } + + /* Version mismatch, do version negotiation. */ + if (!quiche_version_is_supported(pkt_version)) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic version negotiation"); + + ssize_t written = quiche_negotiate_version(scid, scid_len, + dcid, dcid_len, + out, sizeof(out)); + + if (written < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to create quic vneg packet: %d", written); + return NGX_ERROR; + } + + if (ngx_quic_send_udp_packet(c, out, written) == NGX_ERROR) { + return NGX_ERROR; + } + + return NGX_DONE; + } + + /* Initialize source connection ID with some random bytes. */ + RAND_bytes(scid, sizeof(scid)); + +#if (NGX_DEBUG) + { + uint8_t dcid_hex[QUICHE_MAX_CONN_ID_LEN * 2], + scid_hex[QUICHE_MAX_CONN_ID_LEN * 2]; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "new quic connection dcid:%*.s new_scid:%*.s", + ngx_hex_dump(dcid_hex, dcid, dcid_len) - dcid_hex, dcid_hex, + ngx_hex_dump(scid_hex, scid, sizeof(scid)) - scid_hex, scid_hex); + } +#endif + + conn = quiche_conn_new_with_tls(scid, sizeof(scid), NULL, 0, + c->sockaddr, c->socklen, quic->config, + c->ssl->connection, true); + if (conn == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create quic connection"); + return NGX_ERROR; + } + + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + quiche_conn_free(conn); + return NGX_ERROR; + } + + qc->handler = NULL; + + qc->conn = conn; + + c->quic = qc; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handshake(ngx_connection_t *c) +{ + u_char *buf; + size_t buf_len; + ssize_t done; + + quiche_recv_info recv_info = { + c->sockaddr, + c->socklen, + }; + + /* Process the client's Initial packet, which was saved into c->buffer by + * ngx_event_recvmsg(). */ + buf = c->buffer->pos; + buf_len = ngx_buf_size(c->buffer); + + done = quiche_conn_recv(c->quic->conn, buf, buf_len, &recv_info); + + if ((done < 0) && (done != QUICHE_ERR_DONE)) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to process quic packet: %d", done); + return NGX_ERROR; + } + + c->read->handler = ngx_quic_read_handler; + c->write->handler = ngx_quic_write_handler; + + ngx_post_event(c->write, &ngx_posted_events); + + return NGX_AGAIN; +} + + +static void +ngx_quic_read_handler(ngx_event_t *rev) +{ + int n; + static uint8_t buf[65535]; + ngx_connection_t *c; + + c = rev->data; + + c->log->action = "reading QUIC packets"; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic read handler"); + + if (rev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection timed out"); + + if (c->quic->handler != NULL) { + c->quic->handler(c); + } + + return; + } + + for (;;) { + n = c->recv(c, buf, sizeof(buf)); + if (n == NGX_AGAIN) { + break; + } + + if (n == NGX_ERROR) { + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + + quiche_recv_info recv_info = { + c->sockaddr, + c->socklen, + }; + + ssize_t done = quiche_conn_recv(c->quic->conn, buf, n, &recv_info); + + if (done == QUICHE_ERR_DONE) { + break; + } + + if (done < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to process quic packet: %d", done); + + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + } + + if (quiche_conn_is_in_early_data(c->quic->conn) || + quiche_conn_is_established(c->quic->conn)) { + if (!c->ssl->handshaked) { + ngx_quic_handshake_completed(c); + } + + if ((c->quic == NULL) || (c->quic->handler == NULL)) { + return; + } + + /* Notify application layer that there might be stream data to read. */ + c->quic->handler(c); + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic done reading"); + + ngx_post_event(c->write, &ngx_posted_events); +} + + +static void +ngx_quic_write_handler(ngx_event_t *wev) +{ + ngx_connection_t *c; + quiche_send_info send_info; + static uint8_t out[MAX_DATAGRAM_SIZE]; + + c = wev->data; + + c->log->action = "writing QUIC packets"; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic write handler"); + + if (wev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alarm fired"); + + quiche_conn_on_timeout(c->quic->conn); + } + + if (quiche_conn_is_closed(c->quic->conn)) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection is closed"); + + ngx_quic_finalize_connection(c, NGX_QUIC_NO_ERROR); + return; + } + + for (;;) { + ssize_t written = quiche_conn_send(c->quic->conn, out, sizeof(out), + &send_info); + + if (written == QUICHE_ERR_DONE) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic done writing"); + break; + } + + if (written < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to create quic packet: %d", written); + + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + + int rc = ngx_quic_send_udp_packet(c, out, written); + + if (rc == NGX_AGAIN) { + break; + } + + if (rc == NGX_ERROR) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to send quic packet"); + + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + } + + ngx_quic_set_timer(c); +} + + +static void +ngx_quic_set_timer(ngx_connection_t *c) +{ + uint64_t expiry; + ngx_event_t *wev; + + wev = c->write; + + expiry = quiche_conn_timeout_as_millis(c->quic->conn); + expiry = ngx_max(expiry, 1); + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + /* quiche_conn_timeout_as_millis() will return UINT64_MAX when the timer + * should be unset (this would be equvalent to returning Option::None in + * Rust). To avoid overflow we need to explicitly check for this value. */ + if (expiry != UINT64_MAX) { + ngx_add_timer(wev, (ngx_msec_t)expiry); + } +} + + +static void +ngx_quic_handshake_completed(ngx_connection_t *c) +{ +#if (NGX_DEBUG) + { + char buf[129], *s, *d; +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + const +#endif + SSL_CIPHER *cipher; + + cipher = SSL_get_current_cipher(c->ssl->connection); + + if (cipher) { + SSL_CIPHER_description(cipher, &buf[1], 128); + + for (s = &buf[1], d = buf; *s; s++) { + if (*s == ' ' && *d == ' ') { + continue; + } + + if (*s == LF || *s == CR) { + continue; + } + + *++d = *s; + } + + if (*d != ' ') { + d++; + } + + *d = '\0'; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "QUIC: %s, cipher: \"%s\"", + SSL_get_version(c->ssl->connection), &buf[1]); + + if (SSL_session_reused(c->ssl->connection)) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic reused session"); + } + + } else { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic no shared ciphers"); + } + } +#endif + + ngx_del_timer(c->read); + + c->ssl->handshaked = 1; + + /* Notify application layer that the handshake is complete. */ + c->ssl->handler(c); +} + + +ngx_int_t +ngx_quic_shutdown(ngx_connection_t *c) +{ + ssize_t written; + quiche_send_info send_info; + static uint8_t out[MAX_DATAGRAM_SIZE]; + + /* Connection is closed, free memory. */ + if (quiche_conn_is_closed(c->quic->conn)) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "free quic connection"); + + quiche_conn_free(c->quic->conn); + + c->quic = NULL; + c->ssl = NULL; + + return NGX_OK; + } + + /* We can't free the connection state yet, as we need to wait for the + * draining timeout to expire. + * + * Setup event handlers such that we will try again when that happens (or + * when another event is triggered). */ + c->read->handler = ngx_quic_shutdown_handler; + c->write->handler = ngx_quic_shutdown_handler; + + /* Try sending a packet in order to flush pending frames (CONNECTION_CLOSE + * for example), but ignore errors as we are already closing the connection + * anyway. */ + written = quiche_conn_send(c->quic->conn, out, sizeof(out), &send_info); + + if (written > 0) { + ngx_quic_send_udp_packet(c, out, written); + } + + ngx_quic_set_timer(c); + + return NGX_AGAIN; +} + + +static void +ngx_quic_shutdown_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_connection_handler_pt handler; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic shutdown handler"); + + c = ev->data; + handler = c->quic->handler; + + if (ev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alarm fired"); + + quiche_conn_on_timeout(c->quic->conn); + } + + if (ngx_quic_shutdown(c) == NGX_AGAIN) { + return; + } + + handler(c); +} + + +static void +ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t status) +{ + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "finalize quic connection: %d", c->fd); + + c->error = 1; + + if (quiche_conn_is_closed(c->quic->conn)) { + c->close = 1; + } + + quiche_conn_close(c->quic->conn, false, status, NULL, 0); + + /* Notify the application layer that the connection is in an error + * state and will be closed. */ + if (c->quic->handler != NULL) { + c->quic->handler(c); + return; + } + + ngx_quic_close_connection(c); +} + + +static void +ngx_quic_close_connection(ngx_connection_t *c) +{ + ngx_pool_t *pool; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "close quic connection: %d", c->fd); + + if (c->quic) { + if (ngx_quic_shutdown(c) == NGX_AGAIN) { + c->quic->handler = ngx_quic_close_connection; + return; + } + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} + + +void +ngx_quic_cleanup_ctx(void *data) +{ + ngx_quic_t *quic = data; + + quiche_config_free(quic->config); +} + + +static ngx_int_t +ngx_quic_send_udp_packet(ngx_connection_t *c, uint8_t *buf, size_t len) +{ + ngx_buf_t out_buf = {0}; + ngx_chain_t out_chain = {0}; + ngx_chain_t *cl; + + /* The send_chain() API takes an ngx_chain_t parameter instead of a simple + * buffer, so we need to initialize the chain such that it contains only a + * single buffer. + * + * The c->send_chain() call is required (instead of just c->send()) because + * it uses the sendmsg(2) syscall (instead of sendto(2)), which allows us to + * specify the correct source IP address for the connection. */ + + out_buf.start = out_buf.pos = buf; + out_buf.end = out_buf.last = buf + len; + out_buf.memory = 1; + out_buf.flush = 1; + + out_chain.buf = &out_buf; + out_chain.next = NULL; + + c->write->ready = 1; + + cl = c->send_chain(c, &out_chain, 0); + + if (cl != NULL) { + return NGX_AGAIN; + } + + if (cl == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + return NGX_OK; +} diff --color -uNr a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h --- a/src/event/ngx_event_quic.h 1970-01-01 08:00:00.000000000 +0800 +++ b/src/event/ngx_event_quic.h 2021-09-09 19:04:29.391062714 +0800 @@ -0,0 +1,49 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_H_INCLUDED_ +#define _NGX_EVENT_QUIC_H_INCLUDED_ + + +#include + +#include +#include + +#include + +typedef struct ngx_quic_s ngx_quic_t; +typedef struct ngx_quic_connection_s ngx_quic_connection_t; + +struct ngx_quic_s { + quiche_config *config; + ngx_log_t *log; +}; + +struct ngx_quic_connection_s { + quiche_conn *conn; + + ngx_connection_handler_pt handler; +}; + + +ngx_int_t ngx_quic_create_conf(ngx_quic_t *quic); + +ngx_int_t ngx_quic_validate_initial(ngx_event_t *ev, u_char *buf, + ssize_t buf_len); + +ngx_int_t ngx_quic_create_connection(ngx_quic_t *quic, ngx_connection_t *c); + +ngx_int_t ngx_quic_create_ssl_connection(ngx_ssl_t *ssl, ngx_connection_t *c, + ngx_uint_t flags); + +ngx_int_t ngx_quic_handshake(ngx_connection_t *c); + +ngx_int_t ngx_quic_shutdown(ngx_connection_t *c); + +void ngx_quic_cleanup_ctx(void *data); + +#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --color -uNr a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c --- a/src/event/ngx_event_udp.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/event/ngx_event_udp.c 2021-09-09 19:04:29.391062714 +0800 @@ -276,6 +276,14 @@ (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1); #endif +#if (NGX_QUIC) + if (ls->quic) { + if (ngx_quic_validate_initial(ev, buffer, n) != NGX_OK) { + goto next; + } + } +#endif + ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n; diff --color -uNr a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.c 2021-09-09 19:04:41.615382855 +0800 @@ -301,6 +301,41 @@ offsetof(ngx_http_ssl_srv_conf_t, reject_handshake), NULL }, + { ngx_string("ssl_dyn_rec_enable"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_enable), + NULL }, + + { ngx_string("ssl_dyn_rec_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_timeout), + NULL }, + + { ngx_string("ssl_dyn_rec_size_lo"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_lo), + NULL }, + + { ngx_string("ssl_dyn_rec_size_hi"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_hi), + NULL }, + + { ngx_string("ssl_dyn_rec_threshold"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_threshold), + NULL }, + ngx_null_command }; @@ -423,7 +458,7 @@ #if (NGX_DEBUG) unsigned int i; #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) ngx_http_connection_t *hc; #endif #if (NGX_HTTP_V2 || NGX_DEBUG) @@ -440,9 +475,11 @@ } #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) hc = c->data; +#endif +#if (NGX_HTTP_V2) if (hc->addr_conf->http2) { srv = (unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; @@ -450,6 +487,13 @@ } else #endif +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + srv = (unsigned char *) QUICHE_H3_APPLICATION_PROTOCOL; + srvlen = sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1; + + } else +#endif { srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE; srvlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1; @@ -637,6 +681,11 @@ sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; sscf->stapling = NGX_CONF_UNSET; sscf->stapling_verify = NGX_CONF_UNSET; + sscf->dyn_rec_enable = NGX_CONF_UNSET; + sscf->dyn_rec_timeout = NGX_CONF_UNSET_MSEC; + sscf->dyn_rec_size_lo = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_size_hi = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_threshold = NGX_CONF_UNSET_UINT; return sscf; } @@ -712,6 +761,20 @@ ngx_conf_merge_str_value(conf->stapling_responder, prev->stapling_responder, ""); + ngx_conf_merge_value(conf->dyn_rec_enable, prev->dyn_rec_enable, 0); + ngx_conf_merge_msec_value(conf->dyn_rec_timeout, prev->dyn_rec_timeout, + 1000); + /* Default sizes for the dynamic record sizes are defined to fit maximal + TLS + IPv6 overhead in a single TCP segment for lo and 3 segments for hi: + 1369 = 1500 - 40 (IP) - 20 (TCP) - 10 (Time) - 61 (Max TLS overhead) */ + ngx_conf_merge_size_value(conf->dyn_rec_size_lo, prev->dyn_rec_size_lo, + 1369); + /* 4229 = (1500 - 40 - 20 - 10) * 3 - 61 */ + ngx_conf_merge_size_value(conf->dyn_rec_size_hi, prev->dyn_rec_size_hi, + 4229); + ngx_conf_merge_uint_value(conf->dyn_rec_threshold, prev->dyn_rec_threshold, + 40); + conf->ssl.log = cf->log; if (conf->enable) { @@ -943,6 +1006,28 @@ return NGX_CONF_ERROR; } + if (conf->dyn_rec_enable) { + conf->ssl.dyn_rec.timeout = conf->dyn_rec_timeout; + conf->ssl.dyn_rec.threshold = conf->dyn_rec_threshold; + + if (conf->buffer_size > conf->dyn_rec_size_lo) { + conf->ssl.dyn_rec.size_lo = conf->dyn_rec_size_lo; + + } else { + conf->ssl.dyn_rec.size_lo = conf->buffer_size; + } + + if (conf->buffer_size > conf->dyn_rec_size_hi) { + conf->ssl.dyn_rec.size_hi = conf->dyn_rec_size_hi; + + } else { + conf->ssl.dyn_rec.size_hi = conf->buffer_size; + } + + } else { + conf->ssl.dyn_rec.timeout = 0; + } + return NGX_CONF_OK; } diff --color -uNr a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.h 2021-09-09 19:04:41.616382881 +0800 @@ -67,6 +67,12 @@ u_char *file; ngx_uint_t line; + + ngx_flag_t dyn_rec_enable; + ngx_msec_t dyn_rec_timeout; + size_t dyn_rec_size_lo; + size_t dyn_rec_size_hi; + ngx_uint_t dyn_rec_threshold; } ngx_http_ssl_srv_conf_t; diff --color -uNr a/src/http/ngx_http.c b/src/http/ngx_http.c --- a/src/http/ngx_http.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/ngx_http.c 2021-09-09 19:04:29.392062740 +0800 @@ -1178,6 +1178,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, ngx_http_listen_opt_t *lsopt) { + int t; in_port_t p; ngx_uint_t i; struct sockaddr *sa; @@ -1196,11 +1197,13 @@ sa = lsopt->sockaddr; p = ngx_inet_get_port(sa); + t = lsopt->quic ? SOCK_DGRAM : SOCK_STREAM; port = cmcf->ports->elts; for (i = 0; i < cmcf->ports->nelts; i++) { - if (p != port[i].port || sa->sa_family != port[i].family) { + if (p != port[i].port || sa->sa_family != port[i].family + || t != port[i].type) { continue; } @@ -1219,6 +1222,7 @@ port->family = sa->sa_family; port->port = p; port->addrs.elts = NULL; + port->type = t; return ngx_http_add_address(cf, cscf, port, lsopt); } @@ -1236,6 +1240,9 @@ #if (NGX_HTTP_V2) ngx_uint_t http2; #endif +#if (NGX_HTTP_V3) + ngx_uint_t quic; +#endif /* * we cannot compare whole sockaddr struct's as kernel @@ -1271,6 +1278,9 @@ #if (NGX_HTTP_V2) http2 = lsopt->http2 || addr[i].opt.http2; #endif +#if (NGX_HTTP_V3) + quic = lsopt->quic || addr[i].opt.quic; +#endif if (lsopt->set) { @@ -1307,6 +1317,9 @@ #if (NGX_HTTP_V2) addr[i].opt.http2 = http2; #endif +#if (NGX_HTTP_V3) + addr[i].opt.quic = quic; +#endif return NGX_OK; } @@ -1725,6 +1738,12 @@ break; } +#if (NGX_HTTP_V3) + if (addr[i].opt.quic) { + ls->type = SOCK_DGRAM; + } +#endif + addr++; last--; } @@ -1806,6 +1825,12 @@ ls->reuseport = addr->opt.reuseport; #endif +#if (NGX_HTTP_V3) + ls->quic = addr->opt.quic; + + ls->wildcard = addr->opt.wildcard; +#endif + return ls; } @@ -1839,6 +1864,9 @@ addrs[i].conf.http2 = addr[i].opt.http2; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; +#if (NGX_HTTP_V3) + addrs[i].conf.quic = addr[i].opt.quic; +#endif if (addr[i].hash.buckets == NULL && (addr[i].wc_head == NULL @@ -1904,6 +1932,9 @@ addrs6[i].conf.http2 = addr[i].opt.http2; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; +#if (NGX_HTTP_V3) + addrs6[i].conf.quic = addr[i].opt.quic; +#endif if (addr[i].hash.buckets == NULL && (addr[i].wc_head == NULL diff --color -uNr a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/ngx_http_core_module.c 2021-09-09 19:04:29.393062766 +0800 @@ -4191,6 +4191,13 @@ continue; } +#if (NGX_HTTP_V3) + if (ngx_strcmp(value[n].data, "quic") == 0) { + lsopt.quic = 1; + continue; + } +#endif + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[n]); return NGX_CONF_ERROR; diff --color -uNr a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h --- a/src/http/ngx_http_core_module.h 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/ngx_http_core_module.h 2021-09-09 19:04:29.393062766 +0800 @@ -82,6 +82,7 @@ unsigned reuseport:1; unsigned so_keepalive:2; unsigned proxy_protocol:1; + unsigned quic:1; int backlog; int rcvbuf; @@ -238,6 +239,7 @@ unsigned ssl:1; unsigned http2:1; unsigned proxy_protocol:1; + unsigned quic:1; }; @@ -268,6 +270,7 @@ ngx_int_t family; in_port_t port; ngx_array_t addrs; /* array of ngx_http_conf_addr_t */ + ngx_int_t type; } ngx_http_conf_port_t; diff --color -uNr a/src/http/ngx_http.h b/src/http/ngx_http.h --- a/src/http/ngx_http.h 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/ngx_http.h 2021-09-09 19:04:29.392062740 +0800 @@ -20,6 +20,7 @@ typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t; typedef struct ngx_http_chunked_s ngx_http_chunked_t; typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t; +typedef struct ngx_http_v3_stream_s ngx_http_v3_stream_t; typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); @@ -38,6 +39,9 @@ #if (NGX_HTTP_V2) #include #endif +#if (NGX_HTTP_V3) +#include +#endif #if (NGX_HTTP_CACHE) #include #endif diff --color -uNr a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c --- a/src/http/ngx_http_request_body.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/ngx_http_request_body.c 2021-09-09 19:04:29.395062819 +0800 @@ -314,6 +314,12 @@ ngx_del_timer(c->read); } +#if (NGX_HTTP_V3) + if (r->qstream) { + return NGX_AGAIN; + } +#endif + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -418,6 +424,12 @@ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ngx_add_timer(c->read, clcf->client_body_timeout); +#if (NGX_HTTP_V3) + if (r->qstream) { + return NGX_AGAIN; + } +#endif + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -625,6 +637,17 @@ } #endif +#if (NGX_HTTP_V3) + if (r->qstream) { + r->qstream->skip_data = 1; + + /* disable stream read to avoid pointless data events */ + ngx_http_v3_stop_stream_read(r->qstream, 0); + + return NGX_OK; + } +#endif + if (ngx_http_test_expect(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -921,6 +944,9 @@ #if (NGX_HTTP_V2) || r->stream != NULL #endif +#if (NGX_HTTP_V3) + || r->qstream != NULL +#endif ) { return NGX_OK; @@ -960,6 +986,13 @@ static ngx_int_t ngx_http_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { + +#if (NGX_HTTP_V3) + if (r->qstream) { + return ngx_http_v3_request_body_filter(r, in); + } +#endif + if (r->headers_in.chunked) { return ngx_http_request_body_chunked_filter(r, in); diff --color -uNr a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/ngx_http_request.c 2021-09-09 19:04:43.721438005 +0800 @@ -64,6 +64,10 @@ static void ngx_http_ssl_handshake_handler(ngx_connection_t *c); #endif +#if (NGX_HTTP_V3) +static void ngx_http_quic_handshake(ngx_event_t *rev); +#endif + static char *ngx_http_client_errors[] = { @@ -350,6 +354,20 @@ c->log->action = "reading PROXY protocol"; } +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + hc->quic = 1; + c->log->action = "QUIC handshaking"; + + /* We already have a UDP packet in the connection buffer, so we don't + * need to wait for another read event to kick-off the handshake. */ + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_http_quic_handshake(rev); + return; + } +#endif + if (rev->ready) { /* the deferred accept(), iocp */ @@ -805,7 +823,7 @@ c->ssl->no_wait_shutdown = 1; -#if (NGX_HTTP_V2 \ +#if ((NGX_HTTP_V2 || NGX_HTTP_V3) \ && (defined TLSEXT_TYPE_application_layer_protocol_negotiation \ || defined TLSEXT_TYPE_next_proto_neg)) { @@ -815,7 +833,7 @@ hc = c->data; - if (hc->addr_conf->http2) { + if (hc->addr_conf->http2 || hc->addr_conf->quic) { #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation SSL_get0_alpn_selected(c->ssl->connection, &data, &len); @@ -830,11 +848,29 @@ SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len); #endif + } + +#if (NGX_HTTP_V2) + if (hc->addr_conf->http2) { if (len == 2 && data[0] == 'h' && data[1] == '2') { ngx_http_v2_init(c->read); return; } } +#endif + +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + if (len >= 2 && data[0] == 'h' && data[1] == '3') { + ngx_http_v3_init(c->read); + return; + } + + ngx_http_close_connection(c); + return; + } +#endif + } #endif @@ -1059,6 +1095,70 @@ #endif +#if (NGX_HTTP_V3) + +static void +ngx_http_quic_handshake(ngx_event_t *rev) +{ + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_connection_t *hc; + ngx_http_v3_srv_conf_t *qscf; + ngx_http_ssl_srv_conf_t *sscf; + ngx_http_core_srv_conf_t *cscf; + + c = rev->data; + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "http check quic handshake"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_http_close_connection(c); + return; + } + + if (c->close) { + ngx_http_close_connection(c); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "https quic handshake"); + + sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, + ngx_http_ssl_module); + + if (ngx_ssl_create_connection(&sscf->ssl, c, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + qscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + + if (ngx_quic_create_connection(&qscf->quic, c) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + rc = ngx_quic_handshake(c); + + if (rc == NGX_AGAIN) { + + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + + c->ssl->handler = ngx_http_ssl_handshake_handler; + return; + } + + ngx_http_ssl_handshake_handler(c); +} + +#endif + static void ngx_http_process_request_line(ngx_event_t *rev) @@ -2748,6 +2848,13 @@ } #endif +#if (NGX_HTTP_V3) + if (r->qstream) { + ngx_http_close_request(r, 0); + return; + } +#endif + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->main->count != 1) { @@ -2962,6 +3069,19 @@ #endif +#if (NGX_HTTP_V3) + + if (r->qstream) { + if (c->error) { + err = 0; + goto closed; + } + + return; + } + +#endif + #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { @@ -3654,7 +3774,15 @@ } #endif +#if (NGX_HTTP_V3) + if (r->qstream) { + ngx_http_v3_close_stream(r->qstream, rc); + return; + } +#endif + ngx_http_free_request(r, rc); + ngx_http_close_connection(c); } @@ -3775,6 +3903,17 @@ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "close http connection: %d", c->fd); +#if (NGX_HTTP_V3) + + if (c->quic) { + if (ngx_quic_shutdown(c) == NGX_AGAIN) { + c->quic->handler = ngx_http_close_connection; + return; + } + } + +#endif + #if (NGX_HTTP_SSL) if (c->ssl) { diff --color -uNr a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/ngx_http_request.h 2021-09-09 19:04:29.395062819 +0800 @@ -24,6 +24,7 @@ #define NGX_HTTP_VERSION_10 1000 #define NGX_HTTP_VERSION_11 1001 #define NGX_HTTP_VERSION_20 2000 +#define NGX_HTTP_VERSION_3 3000 #define NGX_HTTP_UNKNOWN 0x00000001 #define NGX_HTTP_GET 0x00000002 @@ -327,6 +328,7 @@ ngx_chain_t *free; unsigned ssl:1; + unsigned quic:1; unsigned proxy_protocol:1; } ngx_http_connection_t; @@ -449,6 +451,7 @@ ngx_http_connection_t *http_connection; ngx_http_v2_stream_t *stream; + ngx_http_v3_stream_t *qstream; ngx_http_log_handler_pt log_handler; diff --color -uNr a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/ngx_http_upstream.c 2021-09-09 19:04:29.396062845 +0800 @@ -525,6 +525,13 @@ } #endif +#if (NGX_HTTP_V3) + if (r->qstream) { + ngx_http_upstream_init_request(r); + return; + } +#endif + if (c->read->timer_set) { ngx_del_timer(c->read); } @@ -1357,6 +1364,12 @@ return; } #endif + +#if (NGX_HTTP_V3) + if (r->qstream) { + return; + } +#endif #if (NGX_HAVE_KQUEUE) diff --color -uNr a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c --- a/src/http/v2/ngx_http_v2.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/v2/ngx_http_v2.c 2021-09-09 19:04:41.619382959 +0800 @@ -274,6 +274,8 @@ h2c->frame_size = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + h2c->max_hpack_table_size = NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE; + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); h2c->concurrent_pushes = h2scf->concurrent_pushes; @@ -2283,6 +2285,14 @@ case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING: h2c->table_update = 1; + + if (value > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { + h2c->max_hpack_table_size = NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE; + } else { + h2c->max_hpack_table_size = value; + } + + h2c->indicate_resize = 1; break; default: diff --color -uNr a/src/http/v2/ngx_http_v2_encode.c b/src/http/v2/ngx_http_v2_encode.c --- a/src/http/v2/ngx_http_v2_encode.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_encode.c 2021-09-09 19:04:41.619382959 +0800 @@ -10,7 +10,7 @@ #include -static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, +u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); @@ -40,7 +40,7 @@ } -static u_char * +u_char * ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) { if (value < prefix) { diff --color -uNr a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c --- a/src/http/v2/ngx_http_v2_filter_module.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_filter_module.c 2021-09-09 19:04:41.621383012 +0800 @@ -23,10 +23,53 @@ #define ngx_http_v2_literal_size(h) \ (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1) +#define ngx_http_v2_indexed(i) (128 + (i)) +#define ngx_http_v2_inc_indexed(i) (64 + (i)) + +#define NGX_HTTP_V2_ENCODE_RAW 0 +#define NGX_HTTP_V2_ENCODE_HUFF 0x80 + +#define NGX_HTTP_V2_AUTHORITY_INDEX 1 +#define NGX_HTTP_V2_METHOD_GET_INDEX 2 +#define NGX_HTTP_V2_PATH_INDEX 4 + +#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6 +#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7 + +#define NGX_HTTP_V2_STATUS_INDEX 8 +#define NGX_HTTP_V2_STATUS_200_INDEX 8 +#define NGX_HTTP_V2_STATUS_204_INDEX 9 +#define NGX_HTTP_V2_STATUS_206_INDEX 10 +#define NGX_HTTP_V2_STATUS_304_INDEX 11 +#define NGX_HTTP_V2_STATUS_400_INDEX 12 +#define NGX_HTTP_V2_STATUS_404_INDEX 13 +#define NGX_HTTP_V2_STATUS_500_INDEX 14 + +#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 +#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 +#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 +#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 +#define NGX_HTTP_V2_DATE_INDEX 33 +#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 +#define NGX_HTTP_V2_LOCATION_INDEX 46 +#define NGX_HTTP_V2_SERVER_INDEX 54 +#define NGX_HTTP_V2_USER_AGENT_INDEX 58 +#define NGX_HTTP_V2_VARY_INDEX 59 #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 +static const struct { + u_char *name; + u_char const len; +} push_header[] = { + { (u_char*)":authority" , 10 }, + { (u_char*)"accept-encoding" , 15 }, + { (u_char*)"accept-language" , 15 }, + { (u_char*)"user-agent" , 10 } +}; + + typedef struct { ngx_str_t name; u_char index; @@ -155,11 +198,9 @@ #endif static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER); - static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)]; static size_t nginx_ver_build_len = ngx_http_v2_literal_size(NGINX_VER_BUILD); - static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)]; stream = r->stream; @@ -435,7 +476,7 @@ } tmp = ngx_palloc(r->pool, tmp_len); - pos = ngx_pnalloc(r->pool, len); + pos = ngx_pnalloc(r->pool, len + 15 + 1); if (pos == NULL || tmp == NULL) { return NGX_ERROR; @@ -443,11 +484,16 @@ start = pos; - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; + h2c = r->stream->connection; + + if (h2c->indicate_resize) { + *pos = 32; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), + h2c->max_hpack_table_size); + h2c->indicate_resize = 0; +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_table_resize(h2c); +#endif } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, @@ -458,67 +504,28 @@ *pos++ = status; } else { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); - *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3; - pos = ngx_sprintf(pos, "%03ui", r->headers_out.status); + ngx_sprintf(pos + 8, "%O3ui", r->headers_out.status); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)":status", + sizeof(":status") - 1, pos + 8, 3, tmp); } if (r->headers_out.server == NULL) { - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: %s\"", - NGINX_VER); - - } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: %s\"", - NGINX_VER_BUILD); - - } else { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: nginx\""); - } - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); - - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - if (nginx_ver[0] == '\0') { - p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER, - sizeof(NGINX_VER) - 1, tmp); - nginx_ver_len = p - nginx_ver; - } - - pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len); + pos = ngx_http_v2_write_header_str("server", NGINX_VER); } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - if (nginx_ver_build[0] == '\0') { - p = ngx_http_v2_write_value(nginx_ver_build, - (u_char *) NGINX_VER_BUILD, - sizeof(NGINX_VER_BUILD) - 1, tmp); - nginx_ver_build_len = p - nginx_ver_build; - } - - pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len); + pos = ngx_http_v2_write_header_str("server", NGINX_VER_BUILD); } else { - pos = ngx_cpymem(pos, nginx, sizeof(nginx)); + pos = ngx_http_v2_write_header_str("server", "nginx"); } } if (r->headers_out.date == NULL) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"date: %V\"", - &ngx_cached_http_time); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); - pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data, - ngx_cached_http_time.len, tmp); + pos = ngx_http_v2_write_header_tbl("date", ngx_cached_http_time); } if (r->headers_out.content_type.len) { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); - if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { @@ -544,64 +551,36 @@ r->headers_out.content_type.data = p - len; } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"content-type: %V\"", - &r->headers_out.content_type); - - pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data, - r->headers_out.content_type.len, tmp); + pos = ngx_http_v2_write_header_tbl("content-type", + r->headers_out.content_type); } if (r->headers_out.content_length == NULL && r->headers_out.content_length_n >= 0) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"content-length: %O\"", - r->headers_out.content_length_n); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX); - - p = pos; - pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n); - *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1); + p = ngx_sprintf(pos + 15, "%O", r->headers_out.content_length_n); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"content-length", + sizeof("content-length") - 1, pos + 15, + p - (pos + 15), tmp); } if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); - - ngx_http_time(pos, r->headers_out.last_modified_time); + ngx_http_time(pos + 14, r->headers_out.last_modified_time); len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"last-modified: %*s\"", - len, pos); - - /* - * Date will always be encoded using huffman in the temporary buffer, - * so it's safe here to use src and dst pointing to the same address. - */ - pos = ngx_http_v2_write_value(pos, pos, len, tmp); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"last-modified", + sizeof("last-modified") - 1, pos + 14, + len, tmp); } if (r->headers_out.location && r->headers_out.location->value.len) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"location: %V\"", - &r->headers_out.location->value); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); - pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data, - r->headers_out.location->value.len, tmp); + pos = ngx_http_v2_write_header_tbl("location", r->headers_out.location->value); } #if (NGX_HTTP_GZIP) if (r->gzip_vary) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"vary: Accept-Encoding\""); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); - pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding)); + pos = ngx_http_v2_write_header_str("vary", "Accept-Encoding"); } #endif @@ -624,23 +603,10 @@ continue; } -#if (NGX_DEBUG) - if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { - ngx_strlow(tmp, header[i].key.data, header[i].key.len); - - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"%*s: %V\"", - header[i].key.len, tmp, &header[i].value); - } -#endif - - *pos++ = 0; - - pos = ngx_http_v2_write_name(pos, header[i].key.data, - header[i].key.len, tmp); + pos = ngx_http_v2_write_header(h2c, pos, header[i].key.data, + header[i].key.len, header[i].value.data, + header[i].value.len, tmp); - pos = ngx_http_v2_write_value(pos, header[i].value.data, - header[i].value.len, tmp); } fin = r->header_only @@ -998,6 +964,7 @@ for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { len += binary[i].len; + len += push_header[i].len + 1; } pos = ngx_pnalloc(r->pool, len); @@ -1007,12 +974,17 @@ start = pos; - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; - } + h2c = r->stream->connection; + + if (h2c->indicate_resize) { + *pos = 32; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), + h2c->max_hpack_table_size); + h2c->indicate_resize = 0; +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_table_resize(h2c); +#endif + } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":method: GET\""); @@ -1022,8 +994,7 @@ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":path: %V\"", path); - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); - pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp); + pos = ngx_http_v2_write_header_pot(":path", path); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":scheme: %V\"", &r->schema); @@ -1048,11 +1019,15 @@ continue; } + value = &(*h)->value; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \"%V: %V\"", &ph[i].name, &(*h)->value); - pos = ngx_cpymem(pos, binary[i].data, binary[i].len); + pos = ngx_http_v2_write_header(h2c, pos, + push_header[i].name, push_header[i].len, value->data, value->len, + tmp); } frame = ngx_http_v2_create_push_frame(r, start, pos); diff --color -uNr a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h --- a/src/http/v2/ngx_http_v2.h 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/v2/ngx_http_v2.h 2021-09-09 19:04:41.621383012 +0800 @@ -52,6 +52,14 @@ #define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1) #define NGX_HTTP_V2_DEFAULT_WINDOW 65535 +#define HPACK_ENC_HTABLE_SZ 128 /* better to keep a PoT < 64k */ +#define HPACK_ENC_HTABLE_ENTRIES ((HPACK_ENC_HTABLE_SZ * 100) / 128) +#define HPACK_ENC_DYNAMIC_KEY_TBL_SZ 10 /* 10 is sufficient for most */ +#define HPACK_ENC_MAX_ENTRY 512 /* longest header size to match */ + +#define NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE 4096 +#define NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE 16384 /* < 64k */ + #define NGX_HTTP_V2_DEFAULT_WEIGHT 16 @@ -115,6 +123,46 @@ } ngx_http_v2_hpack_t; +#if (NGX_HTTP_V2_HPACK_ENC) +typedef struct { + uint64_t hash_val; + uint32_t index; + uint16_t pos; + uint16_t klen, vlen; + uint16_t size; + uint16_t next; +} ngx_http_v2_hpack_enc_entry_t; + + +typedef struct { + uint64_t hash_val; + uint32_t index; + uint16_t pos; + uint16_t klen; +} ngx_http_v2_hpack_name_entry_t; + + +typedef struct { + size_t size; /* size as defined in RFC 7541 */ + uint32_t top; /* the last entry */ + uint32_t pos; + uint16_t n_elems; /* number of elements */ + uint16_t base; /* index of the oldest entry */ + uint16_t last; /* index of the newest entry */ + + /* hash table for dynamic entries, instead using a generic hash table, + which would be too slow to process a significant amount of headers, + this table is not determenistic, and might ocasionally fail to insert + a value, at the cost of slightly worse compression, but significantly + faster performance */ + ngx_http_v2_hpack_enc_entry_t htable[HPACK_ENC_HTABLE_SZ]; + ngx_http_v2_hpack_name_entry_t heads[HPACK_ENC_DYNAMIC_KEY_TBL_SZ]; + u_char storage[NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE + + HPACK_ENC_MAX_ENTRY]; +} ngx_http_v2_hpack_enc_t; +#endif + + struct ngx_http_v2_connection_s { ngx_connection_t *connection; ngx_http_connection_t *http_connection; @@ -136,6 +184,8 @@ size_t frame_size; + size_t max_hpack_table_size; + ngx_queue_t waiting; ngx_http_v2_state_t state; @@ -165,6 +215,11 @@ unsigned blocked:1; unsigned goaway:1; unsigned push_disabled:1; + unsigned indicate_resize:1; + +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_hpack_enc_t hpack_enc; +#endif }; @@ -208,6 +263,8 @@ ngx_array_t *cookies; + size_t header_limit; + ngx_pool_t *pool; unsigned waiting:1; @@ -420,4 +477,35 @@ u_char *tmp, ngx_uint_t lower); +u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, + u_char *tmp, ngx_uint_t lower); + +u_char * +ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); + +#define ngx_http_v2_write_name(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 1) +#define ngx_http_v2_write_value(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 0) + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, u_char *value, size_t value_len, + u_char *tmp); + +void +ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c); + +#define ngx_http_v2_write_header_str(key, value) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + (u_char *) value, sizeof(value) - 1, tmp); + +#define ngx_http_v2_write_header_tbl(key, val) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + val.data, val.len, tmp); + +#define ngx_http_v2_write_header_pot(key, val) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + val->data, val->len, tmp); + #endif /* _NGX_HTTP_V2_H_INCLUDED_ */ diff --color -uNr a/src/http/v2/ngx_http_v2_table.c b/src/http/v2/ngx_http_v2_table.c --- a/src/http/v2/ngx_http_v2_table.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_table.c 2021-09-09 19:04:41.624383090 +0800 @@ -361,3 +361,434 @@ return NGX_OK; } + + +#if (NGX_HTTP_V2_HPACK_ENC) + +static ngx_int_t +hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len); + +static ngx_int_t +hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, + uint8_t *key, size_t key_len); + + +void +ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c) +{ + ngx_http_v2_hpack_enc_entry_t *table; + uint64_t idx; + + table = h2c->hpack_enc.htable; + + while (h2c->hpack_enc.size > h2c->max_hpack_table_size) { + idx = h2c->hpack_enc.base; + h2c->hpack_enc.base = table[idx].next; + h2c->hpack_enc.size -= table[idx].size; + table[idx].hash_val = 0; + h2c->hpack_enc.n_elems--; + } +} + + +/* checks if a header is in the hpack table - if so returns the table entry, + otherwise encodes and inserts into the table and returns 0, + if failed to insert into table, returns -1 */ +static ngx_int_t +ngx_http_v2_table_encode_strings(ngx_http_v2_connection_t *h2c, + size_t key_len, size_t val_len, uint8_t *key, uint8_t *val, + ngx_int_t *header_idx) +{ + uint64_t hash_val, key_hash, idx, lru; + int i; + size_t size = key_len + val_len + 32; + uint8_t *storage = h2c->hpack_enc.storage; + + ngx_http_v2_hpack_enc_entry_t *table; + ngx_http_v2_hpack_name_entry_t *name; + + *header_idx = NGX_ERROR; + /* step 1: compute the hash value of header */ + if (size > HPACK_ENC_MAX_ENTRY || size > h2c->max_hpack_table_size) { + return NGX_ERROR; + } + + key_hash = ngx_murmur_hash2_64(key, key_len, 0x01234); + hash_val = ngx_murmur_hash2_64(val, val_len, key_hash); + + if (hash_val == 0) { + return NGX_ERROR; + } + + /* step 2: check if full header in the table */ + idx = hash_val; + i = -1; + while (idx) { + /* at most 8 locations are checked, but most will be done in 1 or 2 */ + table = &h2c->hpack_enc.htable[idx % HPACK_ENC_HTABLE_SZ]; + if (table->hash_val == hash_val + && table->klen == key_len + && table->vlen == val_len + && ngx_memcmp(key, storage + table->pos, key_len) == 0 + && ngx_memcmp(val, storage + table->pos + key_len, val_len) == 0) + { + return (h2c->hpack_enc.top - table->index) + 61; + } + + if (table->hash_val == 0 && i == -1) { + i = idx % HPACK_ENC_HTABLE_SZ; + break; + } + + idx >>= 8; + } + + /* step 3: check if key is in one of the tables */ + *header_idx = hpack_get_static_index(h2c, key, key_len); + + if (i == -1) { + return NGX_ERROR; + } + + if (*header_idx == NGX_ERROR) { + *header_idx = hpack_get_dynamic_index(h2c, key_hash, key, key_len); + } + + /* step 4: store the new entry */ + table = h2c->hpack_enc.htable; + + if (h2c->hpack_enc.top == 0xffffffff) { + /* just to be on the safe side, avoid overflow */ + ngx_memset(&h2c->hpack_enc, 0, sizeof(ngx_http_v2_hpack_enc_t)); + } + + while ((h2c->hpack_enc.size + size > h2c->max_hpack_table_size) + || h2c->hpack_enc.n_elems == HPACK_ENC_HTABLE_ENTRIES) { + /* make space for the new entry first */ + idx = h2c->hpack_enc.base; + h2c->hpack_enc.base = table[idx].next; + h2c->hpack_enc.size -= table[idx].size; + table[idx].hash_val = 0; + h2c->hpack_enc.n_elems--; + } + + table[i] = (ngx_http_v2_hpack_enc_entry_t){.hash_val = hash_val, + .index = h2c->hpack_enc.top, + .pos = h2c->hpack_enc.pos, + .klen = key_len, + .vlen = val_len, + .size = size, + .next = 0}; + + table[h2c->hpack_enc.last].next = i; + if (h2c->hpack_enc.n_elems == 0) { + h2c->hpack_enc.base = i; + } + + h2c->hpack_enc.last = i; + h2c->hpack_enc.top++; + h2c->hpack_enc.size += size; + h2c->hpack_enc.n_elems++; + + /* update header name lookup */ + if (*header_idx == NGX_ERROR ) { + lru = h2c->hpack_enc.top; + + for (i=0; ihpack_enc.heads[i]; + + if ( name->hash_val == 0 || (name->hash_val == key_hash + && ngx_memcmp(storage + name->pos, key, key_len) == 0) ) + { + name->hash_val = key_hash; + name->pos = h2c->hpack_enc.pos; + name->index = h2c->hpack_enc.top - 1; + break; + } + + if (lru > name->index) { + lru = name->index; + idx = i; + } + } + + if (i == HPACK_ENC_DYNAMIC_KEY_TBL_SZ) { + name = &h2c->hpack_enc.heads[idx]; + name->hash_val = hash_val; + name->pos = h2c->hpack_enc.pos; + name->index = h2c->hpack_enc.top - 1; + } + } + + ngx_memcpy(storage + h2c->hpack_enc.pos, key, key_len); + ngx_memcpy(storage + h2c->hpack_enc.pos + key_len, val, val_len); + + h2c->hpack_enc.pos += size; + if (h2c->hpack_enc.pos > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { + h2c->hpack_enc.pos = 0; + } + + return NGX_OK; +} + + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, + u_char *value, size_t value_len, + u_char *tmp) +{ + ngx_int_t idx, header_idx; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 output header: %*s: %*s", key_len, key, value_len, + value); + + /* attempt to find the value in the dynamic table */ + idx = ngx_http_v2_table_encode_strings(h2c, key_len, value_len, key, value, + &header_idx); + + if (idx > 0) { + /* positive index indicates success */ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Indexed Header Field: %ud", idx); + + *pos = 128; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), idx); + + } else { + + if (header_idx == NGX_ERROR) { /* if key is not present */ + + if (idx == NGX_ERROR) { /* if header was not added */ + *pos++ = 0; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field without" + " Indexing — New Name"); + } else { /* if header was added */ + *pos++ = 64; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field with " + "Incremental Indexing — New Name"); + } + + pos = ngx_http_v2_write_name(pos, key, key_len, tmp); + + } else { /* if key is present */ + + if (idx == NGX_ERROR) { + *pos = 0; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(4), header_idx); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field without" + " Indexing — Indexed Name: %ud", header_idx); + } else { + *pos = 64; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(6), header_idx); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field with " + "Incremental Indexing — Indexed Name: %ud", header_idx); + } + } + + pos = ngx_http_v2_write_value(pos, value, value_len, tmp); + } + + return pos; +} + + +static ngx_int_t +hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, + uint8_t *key, size_t key_len) +{ + ngx_http_v2_hpack_name_entry_t *name; + int i; + + for (i=0; ihpack_enc.heads[i]; + + if (name->hash_val == key_hash + && ngx_memcmp(h2c->hpack_enc.storage + name->pos, key, key_len) == 0) + { + if (name->index >= h2c->hpack_enc.top - h2c->hpack_enc.n_elems) { + return (h2c->hpack_enc.top - name->index) + 61; + } + break; + } + } + + return NGX_ERROR; +} + + +/* decide if a given header is present in the static dictionary, this could be + done in several ways, but it seems the fastest one is "exhaustive" search */ +static ngx_int_t +hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len) +{ + /* the static dictionary of response only headers, + although response headers can be put by origin, + that would be rare */ + static const struct { + u_char len; + const u_char val[28]; + u_char idx; + } server_headers[] = { + { 3, "age", 21},//0 + { 3, "via", 60}, + { 4, "date", 33},//2 + { 4, "etag", 34}, + { 4, "link", 45}, + { 4, "vary", 59}, + { 5, "allow", 22},//6 + { 6, "server", 54},//7 + { 7, "expires", 36},//8 + { 7, "refresh", 52}, + { 8, "location", 46},//10 + {10, "set-cookie", 55},//11 + {11, "retry-after", 53},//12 + {12, "content-type", 31},//13 + {13, "content-range", 30},//14 + {13, "accept-ranges", 18}, + {13, "cache-control", 24}, + {13, "last-modified", 44}, + {14, "content-length", 28},//18 + {16, "content-encoding", 26},//19 + {16, "content-language", 27}, + {16, "content-location", 29}, + {16, "www-authenticate", 61}, + {17, "transfer-encoding", 57},//23 + {18, "proxy-authenticate", 48},//24 + {19, "content-disposition", 25},//25 + {25, "strict-transport-security", 56},//26 + {27, "access-control-allow-origin", 20},//27 + {99, "", 99}, + }, *header; + + /* for a given length, where to start the search + since minimal length is 3, the table has a -3 + offset */ + static const int8_t start_at[] = { + [3-3] = 0, + [4-3] = 2, + [5-3] = 6, + [6-3] = 7, + [7-3] = 8, + [8-3] = 10, + [9-3] = -1, + [10-3] = 11, + [11-3] = 12, + [12-3] = 13, + [13-3] = 14, + [14-3] = 18, + [15-3] = -1, + [16-3] = 19, + [17-3] = 23, + [18-3] = 24, + [19-3] = 25, + [20-3] = -1, + [21-3] = -1, + [22-3] = -1, + [23-3] = -1, + [24-3] = -1, + [25-3] = 26, + [26-3] = -1, + [27-3] = 27, + }; + + uint64_t pref; + size_t save_len = len, i; + int8_t start; + + /* early exit for out of bounds lengths */ + if (len < 3 || len > 27) { + return NGX_ERROR; + } + + start = start_at[len - 3]; + if (start == -1) { + /* exit for non existent lengths */ + return NGX_ERROR; + } + + header = &server_headers[start_at[len - 3]]; + + /* load first 8 bytes of key, for fast comparison */ + if (len < 8) { + pref = 0; + if (len >= 4) { + pref = *(uint32_t *)(val + len - 4) | 0x20202020; + len -= 4; + } + while (len > 0) { /* 3 iterations at most */ + pref = (pref << 8) ^ (val[len - 1] | 0x20); + len--; + } + } else { + pref = *(uint64_t *)val | 0x2020202020202020; + len -= 8; + } + + /* iterate over headers with the right length */ + while (header->len == save_len) { + /* quickly compare the first 8 bytes, most tests will end here */ + if (pref != *(uint64_t *) header->val) { + header++; + continue; + } + + if (len == 0) { + /* len == 0, indicates prefix held the entire key */ + return header->idx; + } + /* for longer keys compare the rest */ + i = 1 + (save_len + 7) % 8; /* align so we can compare in quadwords */ + + while (i + 8 <= save_len) { /* 3 iterations at most */ + if ( *(uint64_t *)&header->val[i] + != (*(uint64_t *) &val[i]| 0x2020202020202020) ) + { + header++; + i = 0; + break; + } + i += 8; + } + + if (i == 0) { + continue; + } + + /* found the corresponding entry in the static dictionary */ + return header->idx; + } + + return NGX_ERROR; +} + +#else + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, + u_char *value, size_t value_len, + u_char *tmp) +{ + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 output header: %*s: %*s", key_len, key, value_len, + value); + + *pos++ = 64; + pos = ngx_http_v2_write_name(pos, key, key_len, tmp); + pos = ngx_http_v2_write_value(pos, value, value_len, tmp); + + return pos; +} + +#endif diff --color -uNr a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c --- a/src/http/v3/ngx_http_v3.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3.c 2021-09-09 19:04:29.397062871 +0800 @@ -0,0 +1,2231 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#include +#include +#include +#include + + +typedef struct { + ngx_str_t name; + ngx_uint_t offset; + ngx_uint_t hash; + ngx_http_header_t *hh; +} ngx_http_v3_parse_header_t; + + +/* errors */ +#define NGX_HTTP_V3_NO_ERROR 0x0100 +#define NGX_HTTP_V3_PROTOCOL_ERROR 0x0101 +#define NGX_HTTP_V3_INTERNAL_ERROR 0x0102 + + +static void ngx_http_v3_handler(ngx_connection_t *c); + +static void ngx_http_v3_idle_handler(ngx_connection_t *c); + +static void ngx_http_v3_handle_connection(ngx_http_v3_connection_t *h3c); + +static ngx_http_v3_stream_t *ngx_http_v3_stream_lookup( + ngx_http_v3_connection_t *h3c, ngx_uint_t stream_id); +static ngx_http_v3_stream_t *ngx_http_v3_create_stream( + ngx_http_v3_connection_t *h3c); +static void ngx_http_v3_close_stream_handler(ngx_event_t *ev); + +static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r, + ngx_http_v3_header_t *header); +static ngx_int_t ngx_http_v3_pseudo_header(ngx_http_request_t *r, + ngx_http_v3_header_t *header); +static ngx_int_t ngx_http_v3_parse_path(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v3_parse_method(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v3_parse_scheme(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v3_parse_authority(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, + ngx_http_v3_parse_header_t *header, ngx_str_t *value); +static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r, + ngx_http_v3_header_t *header); +static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_construct_request_line(ngx_http_request_t *r); + +static void ngx_http_v3_run_request(ngx_http_request_t *r); + +static ssize_t ngx_http_v3_recv_body(ngx_connection_t *c, u_char *buf, + size_t size); +static ngx_chain_t *ngx_http_v3_send_chain(ngx_connection_t *fc, + ngx_chain_t *in, off_t limit); + +static void ngx_http_v3_finalize_connection(ngx_http_v3_connection_t *h3c, + ngx_uint_t status); + +static void ngx_http_v3_pool_cleanup(void *data); + + +static ngx_http_v3_parse_header_t ngx_http_v3_parse_headers[] = { + { ngx_string("host"), + offsetof(ngx_http_headers_in_t, host), 0, NULL }, + + { ngx_string("accept-encoding"), + offsetof(ngx_http_headers_in_t, accept_encoding), 0, NULL }, + + { ngx_string("accept-language"), + offsetof(ngx_http_headers_in_t, accept_language), 0, NULL }, + + { ngx_string("user-agent"), + offsetof(ngx_http_headers_in_t, user_agent), 0, NULL }, + + { ngx_null_string, 0, 0, NULL } +}; + + +void +ngx_http_v3_init(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_connection_t *h3c; + + c = rev->data; + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "init http3 connection"); + + c->log->action = "processing HTTP/3 connection"; + + h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t)); + if (h3c == NULL) { + ngx_http_close_connection(c); + return; + } + + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + + h3c->h3 = quiche_h3_conn_new_with_transport(c->quic->conn, h3scf->http3); + if (h3c->h3 == NULL) { + ngx_http_close_connection(c); + return; + } + + h3c->http_connection = hc; + + h3c->connection = c; + + h3c->pool = c->pool; + + c->data = h3c; + + c->quic->handler = ngx_http_v3_handler; + + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_v3_pool_cleanup; + cln->data = h3c; + + ngx_rbtree_init(&h3c->streams, &h3c->streams_sentinel, + ngx_rbtree_insert_value); +} + + +static int +ngx_http_v3_for_each_header(uint8_t *name, size_t name_len, + uint8_t *value, size_t value_len, void *argp) +{ + ngx_int_t rc; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_request_t *r; + ngx_http_v3_header_t header; + ngx_http_core_srv_conf_t *cscf; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + r = argp; + + /* Duplicate the header name because we don't own it. */ + header.name.data = ngx_pnalloc(r->pool, name_len); + if (header.name.data == NULL) { + return NGX_ERROR; + } + header.name.len = name_len; + + ngx_memcpy(header.name.data, name, name_len); + + /* Duplicate the header value because we don't own it. Some of the + * functions that process headers require a NULL-terminated string, + * so allocate enough memory for that. */ + header.value.data = ngx_pcalloc(r->pool, value_len + 1); + if (header.value.data == NULL) { + return NGX_ERROR; + } + header.value.len = value_len; + + ngx_memcpy(header.value.data, value, value_len); + + if (ngx_http_v3_validate_header(r, &header) != NGX_OK) { + return NGX_ERROR; + } + + /* Check for pseudo-header. */ + if (header.name.data[0] == ':') { + rc = ngx_http_v3_pseudo_header(r, &header); + + if (rc == NGX_OK) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \":%V: %V\"", + &header.name, &header.value); + + return NGX_OK; + } + + return NGX_ERROR; + } + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%V\"", &header.name); + + return NGX_ERROR; + } + } + + /* Handle Cookie header separately. Not sure why, but the HTTP/2 code does + * the same. */ + if (header.name.len == cookie.len + && ngx_memcmp(header.name.data, cookie.data, cookie.len) == 0) + { + if (ngx_http_v3_cookie(r, &header) != NGX_OK) { + return NGX_ERROR; + } + + } else { + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key.len = header.name.len; + h->key.data = header.name.data; + + /* + * TODO Optimization: precalculate hash + * and handler for indexed headers. + */ + h->hash = ngx_hash_key(h->key.data, h->key.len); + + h->value.len = header.value.len; + h->value.data = header.value.data; + + h->lowcase_key = h->key.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \"%V: %V\"", + &header.name, &header.value); + + return NGX_OK; +} + + +static void +ngx_http_v3_process_headers(ngx_connection_t *c, quiche_h3_event *ev, + int64_t stream_id) +{ + int rc; + ngx_http_v3_stream_t *stream; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_connection_t *h3c; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 process headers"); + + h3c = c->data; + + h3scf = ngx_http_get_module_srv_conf(h3c->http_connection->conf_ctx, + ngx_http_v3_module); + + if (h3c->connection->requests >= h3scf->max_requests) { + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_NO_ERROR); + return; + } + + /* Create a new stream to handle the incoming request. */ + stream = ngx_http_v3_create_stream(h3c); + if (stream == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create HTTP/3 stream"); + + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_INTERNAL_ERROR); + return; + } + + stream->id = stream_id; + + stream->node.key = stream_id; + + ngx_rbtree_insert(&h3c->streams, &stream->node); + + /* Populate ngx_http_request_t from raw HTTP/3 headers. */ + rc = quiche_h3_event_for_each_header(ev, + ngx_http_v3_for_each_header, stream->request); + + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "received invalid HTTP/3 headers"); + + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_INTERNAL_ERROR); + return; + } + + stream->in_closed = !quiche_h3_event_headers_has_body(ev); + + ngx_http_v3_run_request(stream->request); +} + + +static void +ngx_http_v3_process_blocked_streams(ngx_http_v3_connection_t *h3c) +{ + ngx_event_t *wev; + quiche_stream_iter *writable; + ngx_http_v3_stream_t *stream; + uint64_t stream_id; + + writable = quiche_conn_writable(h3c->connection->quic->conn); + + while (quiche_stream_iter_next(writable, &stream_id)) { + stream = ngx_http_v3_stream_lookup(h3c, stream_id); + + if (stream == NULL) { + continue; + } + + if (!stream->blocked) { + continue; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 stream unblocked %ui", stream->id); + + stream->blocked = 0; + + wev = stream->request->connection->write; + + wev->active = 0; + wev->ready = 1; + + if (!stream->headers_sent) { + ngx_http_v3_send_response(stream->request); + } + + if (!wev->delayed) { + wev->handler(wev); + } + } + + quiche_stream_iter_free(writable); +} + + +static void +ngx_http_v3_handler(ngx_connection_t *c) +{ + ngx_chain_t out; + ngx_connection_t *fc; + ngx_http_request_t *r; + ngx_http_v3_connection_t *h3c; + ngx_http_v3_stream_t *stream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 handler"); + + h3c = c->data; + + if (c->read->timedout) { + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_PROTOCOL_ERROR); + return; + } + + if (c->error) { + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_INTERNAL_ERROR); + return; + } + + ngx_http_v3_process_blocked_streams(h3c); + + while (!c->error) { + quiche_h3_event *ev; + + int64_t stream_id = quiche_h3_conn_poll(h3c->h3, c->quic->conn, &ev); + if (stream_id == QUICHE_H3_ERR_DONE) { + break; + } + + if (stream_id < 0) { + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_PROTOCOL_ERROR); + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 event stream:%ui ev:%ui", stream_id, + quiche_h3_event_type(ev)); + + switch (quiche_h3_event_type(ev)) { + case QUICHE_H3_EVENT_HEADERS: { + ngx_http_v3_process_headers(c, ev, stream_id); + break; + } + + case QUICHE_H3_EVENT_DATA: { + /* Lookup stream. If there isn't one, it means it has already + * been closed, so ignore the event. */ + stream = ngx_http_v3_stream_lookup(h3c, stream_id); + + if (stream != NULL && !stream->in_closed) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 data"); + + ngx_post_event(stream->request->connection->read, + &ngx_posted_events); + } + + break; + } + + case QUICHE_H3_EVENT_FINISHED: { + /* Lookup stream. If there isn't one, it means it has already + * been closed, so ignore the event. */ + stream = ngx_http_v3_stream_lookup(h3c, stream_id); + + if (stream != NULL && !stream->in_closed) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 finished"); + + /* Flush request body that was buffered. */ + if (stream->request->request_body) { + out.buf = stream->request->request_body->buf; + out.next = NULL; + + ngx_http_v3_request_body_filter(stream->request, &out); + + ngx_post_event(stream->request->connection->read, + &ngx_posted_events); + } + + stream->in_closed = 1; + } + + break; + } + + case QUICHE_H3_EVENT_RESET: { + /* Lookup stream. If there isn't one, it means it has already + * been closed, so ignore the event. */ + stream = ngx_http_v3_stream_lookup(h3c, stream_id); + + if (stream != NULL && !stream->in_closed) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 reset"); + + r = stream->request; + fc = r->connection; + + fc->error = 1; + + ngx_post_event(stream->request->connection->read, + &ngx_posted_events); + + stream->in_closed = 1; + } + + break; + } + + case QUICHE_H3_EVENT_DATAGRAM: + break; + + case QUICHE_H3_EVENT_GOAWAY: + break; + } + + quiche_h3_event_free(ev); + } + + ngx_http_v3_handle_connection(h3c); +} + + +static void +ngx_http_v3_idle_handler(ngx_connection_t *c) +{ + ngx_http_v3_connection_t *h3c; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 idle handler"); + + h3c = c->data; + + if (c->read->timedout) { + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_NO_ERROR); + return; + } + + if (c->error) { + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_INTERNAL_ERROR); + return; + } + + if (!quiche_conn_is_readable(c->quic->conn)) { + return; + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + c->quic->handler = ngx_http_v3_handler; + + ngx_http_v3_handler(c); +} + + +static void +ngx_http_v3_handle_connection(ngx_http_v3_connection_t *h3c) +{ + ngx_connection_t *c; + ngx_http_v3_srv_conf_t *h3scf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 handle connection"); + + c = h3c->connection; + + if (h3c->processing || c->error) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 connection is idle"); + + h3scf = ngx_http_get_module_srv_conf(h3c->http_connection->conf_ctx, + ngx_http_v3_module); + + c->quic->handler = ngx_http_v3_idle_handler; + + ngx_add_timer(c->read, h3scf->idle_timeout); +} + + +static ngx_http_v3_stream_t * +ngx_http_v3_create_stream(ngx_http_v3_connection_t *h3c) +{ + ngx_log_t *log; + ngx_event_t *rev, *wev; + ngx_connection_t *fc; + ngx_http_log_ctx_t *ctx; + ngx_http_request_t *r; + ngx_http_v3_stream_t *stream; + ngx_http_core_srv_conf_t *cscf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 create stream"); + + fc = h3c->free_fake_connections; + + if (fc) { + h3c->free_fake_connections = fc->data; + + rev = fc->read; + wev = fc->write; + log = fc->log; + ctx = log->data; + + } else { + fc = ngx_palloc(h3c->pool, sizeof(ngx_connection_t)); + if (fc == NULL) { + return NULL; + } + + rev = ngx_palloc(h3c->pool, sizeof(ngx_event_t)); + if (rev == NULL) { + return NULL; + } + + wev = ngx_palloc(h3c->pool, sizeof(ngx_event_t)); + if (wev == NULL) { + return NULL; + } + + log = ngx_palloc(h3c->pool, sizeof(ngx_log_t)); + if (log == NULL) { + return NULL; + } + + ctx = ngx_palloc(h3c->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + return NULL; + } + + ctx->connection = fc; + ctx->request = NULL; + ctx->current_request = NULL; + } + + ngx_memcpy(log, h3c->connection->log, sizeof(ngx_log_t)); + + log->data = ctx; + + ngx_memzero(rev, sizeof(ngx_event_t)); + + rev->data = fc; + rev->ready = 1; + rev->handler = ngx_http_v3_close_stream_handler; + rev->log = log; + + ngx_memcpy(wev, rev, sizeof(ngx_event_t)); + + wev->write = 1; + + ngx_memcpy(fc, h3c->connection, sizeof(ngx_connection_t)); + + fc->data = h3c->http_connection; + fc->quic = h3c->connection->quic; + fc->read = rev; + fc->write = wev; + fc->sent = 0; + fc->buffer = NULL; + fc->log = log; + fc->buffered = 0; + fc->sndlowat = 1; + fc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; + + fc->recv = ngx_http_v3_recv_body; + + fc->send_chain = ngx_http_v3_send_chain; + fc->need_last_buf = 1; + + r = ngx_http_create_request(fc); + if (r == NULL) { + return NULL; + } + + ngx_str_set(&r->http_protocol, "HTTP/3"); + + r->http_version = NGX_HTTP_VERSION_3; + r->valid_location = 1; + + fc->data = r; + h3c->connection->requests++; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_in = ngx_create_temp_buf(r->pool, + cscf->client_header_buffer_size); + if (r->header_in == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; + + stream = ngx_pcalloc(h3c->pool, sizeof(ngx_http_v3_stream_t)); + if (stream == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->qstream = stream; + + stream->request = r; + stream->connection = h3c; + + h3c->processing++; + + return stream; +} + + +static ngx_http_v3_stream_t * +ngx_http_v3_stream_lookup(ngx_http_v3_connection_t *h3c, ngx_uint_t stream_id) +{ + ngx_rbtree_node_t *node, *sentinel; + + node = h3c->streams.root; + sentinel = h3c->streams.sentinel; + + while (node != sentinel) { + + if (stream_id < node->key) { + node = node->left; + continue; + } + + if (stream_id > node->key) { + node = node->right; + continue; + } + + /* stream_id == node->key */ + + return (ngx_http_v3_stream_t *) node; + } + + /* not found */ + + return NULL; +} + + +/* The following functions are copied from the HTTP/2 module, and adapted to + * work independently. In theory we could refactor the HTTP/2 module to expose + * these functions, but that would be fairly invasive and likely cause more + * merge conflicts in the future. */ + + +static ngx_int_t +ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_http_v3_header_t *header) +{ + u_char ch; + ngx_uint_t i; + ngx_http_core_srv_conf_t *cscf; + + if (header->name.len == 0) { + return NGX_ERROR; + } + + r->invalid_header = 0; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + for (i = (header->name.data[0] == ':'); i != header->name.len; i++) { + ch = header->name.data[i]; + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + if (ch == '\0' || ch == LF || ch == CR || ch == ':' + || (ch >= 'A' && ch <= 'Z')) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", + &header->name); + + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + for (i = 0; i != header->value.len; i++) { + ch = header->value.data[i]; + + if (ch == '\0' || ch == LF || ch == CR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "invalid value: \"%V\"", + &header->name, &header->value); + + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_pseudo_header(ngx_http_request_t *r, ngx_http_v3_header_t *header) +{ + header->name.len--; + header->name.data++; + + switch (header->name.len) { + case 4: + if (ngx_memcmp(header->name.data, "path", sizeof("path") - 1) + == 0) + { + return ngx_http_v3_parse_path(r, &header->value); + } + + break; + + case 6: + if (ngx_memcmp(header->name.data, "method", sizeof("method") - 1) + == 0) + { + return ngx_http_v3_parse_method(r, &header->value); + } + + if (ngx_memcmp(header->name.data, "scheme", sizeof("scheme") - 1) + == 0) + { + return ngx_http_v3_parse_scheme(r, &header->value); + } + + break; + + case 9: + if (ngx_memcmp(header->name.data, "authority", sizeof("authority") - 1) + == 0) + { + return ngx_http_v3_parse_authority(r, &header->value); + } + + break; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent unknown pseudo-header \":%V\"", + &header->name); + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_http_v3_parse_path(ngx_http_request_t *r, ngx_str_t *value) +{ + if (r->unparsed_uri.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :path header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :path header"); + + return NGX_DECLINED; + } + + r->uri_start = value->data; + r->uri_end = value->data + value->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid :path header: \"%V\"", value); + + return NGX_DECLINED; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_request_uri() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_parse_method(ngx_http_request_t *r, ngx_str_t *value) +{ + size_t k, len; + ngx_uint_t n; + const u_char *p, *m; + + /* + * This array takes less than 256 sequential bytes, + * and if typical CPU cache line size is 64 bytes, + * it is prefetched for 4 load operations. + */ + static const struct { + u_char len; + const u_char method[11]; + uint32_t value; + } tests[] = { + { 3, "GET", NGX_HTTP_GET }, + { 4, "POST", NGX_HTTP_POST }, + { 4, "HEAD", NGX_HTTP_HEAD }, + { 7, "OPTIONS", NGX_HTTP_OPTIONS }, + { 8, "PROPFIND", NGX_HTTP_PROPFIND }, + { 3, "PUT", NGX_HTTP_PUT }, + { 5, "MKCOL", NGX_HTTP_MKCOL }, + { 6, "DELETE", NGX_HTTP_DELETE }, + { 4, "COPY", NGX_HTTP_COPY }, + { 4, "MOVE", NGX_HTTP_MOVE }, + { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, + { 4, "LOCK", NGX_HTTP_LOCK }, + { 6, "UNLOCK", NGX_HTTP_UNLOCK }, + { 5, "PATCH", NGX_HTTP_PATCH }, + { 5, "TRACE", NGX_HTTP_TRACE } + }, *test; + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :method header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :method header"); + + return NGX_DECLINED; + } + + r->method_name.len = value->len; + r->method_name.data = value->data; + + len = r->method_name.len; + n = sizeof(tests) / sizeof(tests[0]); + test = tests; + + do { + if (len == test->len) { + p = r->method_name.data; + m = test->method; + k = len; + + do { + if (*p++ != *m++) { + goto next; + } + } while (--k); + + r->method = test->value; + return NGX_OK; + } + + next: + test++; + + } while (--n); + + p = r->method_name.data; + + do { + if ((*p < 'A' || *p > 'Z') && *p != '_' && *p != '-') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", + &r->method_name); + + return NGX_DECLINED; + } + + p++; + + } while (--len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_parse_scheme(ngx_http_request_t *r, ngx_str_t *value) +{ + u_char c, ch; + ngx_uint_t i; + + if (r->schema.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :scheme header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :scheme header"); + + return NGX_DECLINED; + } + + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + continue; + } + + if (((ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.') + && i > 0) + { + continue; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid :scheme header: \"%V\"", value); + + return NGX_DECLINED; + } + + r->schema = *value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_parse_authority(ngx_http_request_t *r, ngx_str_t *value) +{ + return ngx_http_v3_parse_header(r, &ngx_http_v3_parse_headers[0], value); +} + + +static ngx_int_t +ngx_http_v3_parse_header(ngx_http_request_t *r, + ngx_http_v3_parse_header_t *header, ngx_str_t *value) +{ + ngx_table_elt_t *h; + ngx_http_core_main_conf_t *cmcf; + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key.len = header->name.len; + h->key.data = header->name.data; + h->lowcase_key = header->name.data; + + if (header->hh == NULL) { + header->hash = ngx_hash_key(header->name.data, header->name.len); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + header->hh = ngx_hash_find(&cmcf->headers_in_hash, header->hash, + h->lowcase_key, h->key.len); + if (header->hh == NULL) { + return NGX_ERROR; + } + } + + h->hash = header->hash; + + h->value.len = value->len; + h->value.data = value->data; + + if (header->hh->handler(r, h, header->hh->offset) != NGX_OK) { + /* header handler has already finalized request */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_construct_request_line(ngx_http_request_t *r) +{ + u_char *p; + + static const u_char ending[] = " HTTP/3"; + + if (r->method_name.len == 0 + || r->schema.len == 0 + || r->unparsed_uri.len == 0) + { + if (r->method_name.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :method header"); + + } else if (r->schema.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :scheme header"); + + } else { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :path header"); + } + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + r->request_line.len = r->method_name.len + 1 + + r->unparsed_uri.len + + sizeof(ending) - 1; + + p = ngx_pnalloc(r->pool, r->request_line.len + 1); + if (p == NULL) { + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + + *p++ = ' '; + + p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len); + + ngx_memcpy(p, ending, sizeof(ending)); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request line: \"%V\"", &r->request_line); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_cookie(ngx_http_request_t *r, ngx_http_v3_header_t *header) +{ + ngx_str_t *val; + ngx_array_t *cookies; + + cookies = r->qstream->cookies; + + if (cookies == NULL) { + cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t)); + if (cookies == NULL) { + return NGX_ERROR; + } + + r->qstream->cookies = cookies; + } + + val = ngx_array_push(cookies); + if (val == NULL) { + return NGX_ERROR; + } + + val->len = header->value.len; + val->data = header->value.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_construct_cookie_header(ngx_http_request_t *r) +{ + u_char *buf, *p, *end; + size_t len; + ngx_str_t *vals; + ngx_uint_t i; + ngx_array_t *cookies; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + cookies = r->qstream->cookies; + + if (cookies == NULL) { + return NGX_OK; + } + + vals = cookies->elts; + + i = 0; + len = 0; + + do { + len += vals[i].len + 2; + } while (++i != cookies->nelts); + + len -= 2; + + buf = ngx_pnalloc(r->pool, len + 1); + if (buf == NULL) { + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + p = buf; + end = buf + len; + + for (i = 0; /* void */ ; i++) { + + p = ngx_cpymem(p, vals[i].data, vals[i].len); + + if (p == end) { + *p = '\0'; + break; + } + + *p++ = ';'; *p++ = ' '; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( + ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e'); + + h->key.len = cookie.len; + h->key.data = cookie.data; + + h->value.len = len; + h->value.data = buf; + + h->lowcase_key = cookie.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_multi_header_lines() + */ + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_http_v3_run_request(ngx_http_request_t *r) +{ + if (ngx_http_v3_construct_request_line(r) != NGX_OK) { + return; + } + + if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { + return; + } + + r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; + + if (ngx_http_process_request_header(r) != NGX_OK) { + return; + } + + if (r->headers_in.content_length_n > 0 && r->qstream->in_closed) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + + r->qstream->skip_data = 1; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return; + } + + if (r->headers_in.content_length_n == -1 && !r->qstream->in_closed) { + r->headers_in.chunked = 1; + } + + ngx_http_process_request(r); +} + + +/* End of functions copied from HTTP/2 module. */ + + +ngx_int_t +ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + size_t size; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t *cl, *tl, *out, **ll; + ngx_connection_t *c; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + c = r->qstream->connection->connection; + + rb = r->request_body; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (rb->rest == -1) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request body filter"); + + if (r->headers_in.chunked) { + rb->rest = clcf->client_body_buffer_size; + r->headers_in.content_length_n = 0; + } else { + rb->rest = r->headers_in.content_length_n; + } + } + + out = NULL; + ll = &out; + + for (cl = in; cl; cl = cl->next) { + + if (rb->rest == 0) { + break; + } + + if (ngx_buf_size(cl->buf) == 0) { + continue; + } + + tl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (tl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b = tl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->temporary = 1; + b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body; + b->start = cl->buf->pos; + b->pos = cl->buf->pos; + b->last = cl->buf->last; + b->end = cl->buf->end; + b->flush = r->request_body_no_buffering; + + size = cl->buf->last - cl->buf->pos; + + cl->buf->pos = cl->buf->last; + + if (r->headers_in.chunked) { + r->headers_in.content_length_n += size; + } + + if (quiche_conn_stream_finished(c->quic->conn, r->qstream->id)) { + rb->rest = 0; + b->last = cl->buf->pos; + b->last_buf = 1; + } + + *ll = tl; + ll = &tl->next; + } + + rc = ngx_http_top_request_body_filter(r, out); + + ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out, + (ngx_buf_tag_t) &ngx_http_read_client_request_body); + + return rc; +} + + +size_t +ngx_http_v3_get_headers_out_count(ngx_http_request_t *r) +{ + size_t headers_count; + ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *header; + + headers_count = 1; /* :status */ + + if (r->headers_out.server == NULL) { + headers_count += 1; + } + + if (r->headers_out.date == NULL) { + headers_count += 1; + } + + if (r->headers_out.content_type.len) { + headers_count += 1; + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + headers_count += 1; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + headers_count += 1; + } + + if (r->headers_out.location && r->headers_out.location->value.len) { + headers_count += 1; + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + headers_count += 1; + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + headers_count += 1; + } + + return headers_count; +} + + +ngx_int_t +ngx_http_v3_push_response_headers(ngx_http_request_t *r) +{ + u_char *tmp; + size_t len, headers_count; + ngx_str_t host, location; + ngx_uint_t i, port; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_connection_t *fc; + quiche_h3_header *h; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + u_char addr[NGX_SOCKADDR_STRLEN]; + + /* The list of response headers was already generated, so there's nothing + * more to do here. */ + if (r->qstream->headers != NULL) { + return NGX_OK; + } + + fc = r->connection; + + if (r->method == NGX_HTTP_HEAD) { + r->header_only = 1; + } + + switch (r->headers_out.status) { + + case NGX_HTTP_OK: + break; + + case NGX_HTTP_NO_CONTENT: + r->header_only = 1; + + ngx_str_null(&r->headers_out.content_type); + + r->headers_out.content_length = NULL; + r->headers_out.content_length_n = -1; + + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + break; + + case NGX_HTTP_PARTIAL_CONTENT: + break; + + case NGX_HTTP_NOT_MODIFIED: + r->header_only = 1; + break; + + default: + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + } + + headers_count = ngx_http_v3_get_headers_out_count(r); + + r->qstream->headers = + ngx_array_create(r->pool, headers_count, sizeof(quiche_h3_header)); + + if (r->qstream->headers == NULL) { + return NGX_ERROR; + } + + /* Generate :status pseudo-header. */ + { + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) ":status"; + h->name_len = sizeof(":status") - 1; + + tmp = ngx_pnalloc(r->pool, sizeof("418") - 1); + if (tmp == NULL) { + return NGX_ERROR; + } + + h->value = tmp; + h->value_len = ngx_sprintf(tmp, "%03ui", r->headers_out.status) - tmp; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + /* Generate Server header.*/ + if (r->headers_out.server == NULL) { + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "server"; + h->name_len = sizeof("server") - 1; + + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + h->value = (u_char *) NGINX_VER; + h->value_len = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + h->value = (u_char *) NGINX_VER_BUILD; + h->value_len = sizeof(NGINX_VER_BUILD) - 1; + + } else { + h->value = (u_char *) "nginx"; + h->value_len = sizeof("nginx") - 1; + } + } + + /* Generate Date header. */ + if (r->headers_out.date == NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"date: %V\"", + &ngx_cached_http_time); + + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "date"; + h->name_len = sizeof("date") - 1; + + h->value = ngx_cached_http_time.data; + h->value_len = ngx_cached_http_time.len; + } + + /* Generate Content-Type header. */ + if (r->headers_out.content_type.len) { + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + len = r->headers_out.content_type.len + sizeof("; charset=") - 1 + + r->headers_out.charset.len; + + tmp = ngx_pnalloc(r->pool, len); + if (tmp == NULL) { + return NGX_ERROR; + } + + tmp = ngx_cpymem(tmp, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + tmp = ngx_cpymem(tmp, "; charset=", sizeof("; charset=") - 1); + + tmp = ngx_cpymem(tmp, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* updated r->headers_out.content_type is also needed for logging */ + + r->headers_out.content_type.len = len; + r->headers_out.content_type.data = tmp - len; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"content-type: %V\"", + &r->headers_out.content_type); + + h->name = (u_char *) "content-type"; + h->name_len = sizeof("content-type") - 1; + + h->value = r->headers_out.content_type.data; + h->value_len = r->headers_out.content_type.len; + } + + /* Generate Content-Length header. */ + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "content-length"; + h->name_len = sizeof("content-length") - 1; + + tmp = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); + if (tmp == NULL) { + return NGX_ERROR; + } + + h->value = tmp; + h->value_len = + ngx_sprintf(tmp, "%O", r->headers_out.content_length_n) - tmp; + } + + /* Generate Last-Modified header. */ + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "last-modified"; + h->name_len = sizeof("last-modified") - 1; + + tmp = ngx_pnalloc(r->pool, sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1); + if (tmp == NULL) { + return NGX_ERROR; + } + + h->value = tmp; + h->value_len = + ngx_http_time(tmp, r->headers_out.last_modified_time) - tmp; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"last-modified: %*.s\"", + h->value_len, h->value); + } + + /* Generate Location header. */ + if (r->headers_out.location && r->headers_out.location->value.len) { + + if (r->headers_out.location->value.data[0] == '/' + && clcf->absolute_redirect) + { + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.data = addr; + host.len = NGX_SOCKADDR_STRLEN; + + if (ngx_connection_local_sockaddr(fc, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + port = ngx_inet_get_port(fc->local_sockaddr); + + location.len = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + +#if (NGX_HTTP_SSL) + if (fc->ssl) + port = (port == 443) ? 0 : port; + else +#endif + port = (port == 80) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + location.len += sizeof(":65535") - 1; + } + + location.data = ngx_pnalloc(r->pool, location.len); + if (location.data == NULL) { + return NGX_ERROR; + } + + tmp = ngx_cpymem(location.data, "http", sizeof("http") - 1); + +#if (NGX_HTTP_SSL) + if (fc->ssl) { + *tmp++ = 's'; + } +#endif + + *tmp++ = ':'; *tmp++ = '/'; *tmp++ = '/'; + tmp = ngx_cpymem(tmp, host.data, host.len); + + if (port) { + tmp = ngx_sprintf(tmp, ":%ui", port); + } + + tmp = ngx_cpymem(tmp, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = tmp - location.data; + r->headers_out.location->value.data = location.data; + ngx_str_set(&r->headers_out.location->key, "Location"); + } + + r->headers_out.location->hash = 0; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"location: %V\"", + &r->headers_out.location->value); + + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "location"; + h->name_len = sizeof("location") - 1; + + h->value = r->headers_out.location->value.data; + h->value_len = r->headers_out.location->value.len; + } + +#if (NGX_HTTP_GZIP) + /* Generate Vary header. */ + if (r->gzip_vary) { + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"vary: Accept-Encoding\""); + + h->name = (u_char *) "vary"; + h->name_len = sizeof("vary") - 1; + + h->value = (u_char *) "Accept-Encoding"; + h->value_len = sizeof("Accept-Encoding") - 1; + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + /* Generate all other headers. */ + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + h = ngx_array_push(r->qstream->headers); + if (h == NULL) { + return NGX_ERROR; + } + +#if (NGX_DEBUG) + if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"%V: %V\"", + &header[i].key, &header[i].value); + } +#endif + + h->name = header[i].key.data; + h->name_len = header[i].key.len; + + h->value = header[i].value.data; + h->value_len = header[i].value.len; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_send_response(ngx_http_request_t *r) +{ + int rc; + ngx_uint_t fin; + ngx_connection_t *c, *fc; + ngx_http_v3_connection_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 send response stream %ui", r->qstream->id); + + fc = r->connection; + + if (fc->error) { + return NGX_ERROR; + } + + h3c = r->qstream->connection; + c = h3c->connection; + + if (ngx_http_v3_push_response_headers(r) != NGX_OK) { + return NGX_ERROR; + } + + fin = r->header_only + || (r->headers_out.content_length_n == 0 && !r->expect_trailers); + + rc = quiche_h3_send_response(h3c->h3, c->quic->conn, r->qstream->id, + r->qstream->headers->elts, + r->qstream->headers->nelts, + fin); + + if (rc == QUICHE_H3_ERR_STREAM_BLOCKED) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 stream blocked %ui", r->qstream->id); + + r->qstream->blocked = 1; + + fc->write->active = 1; + fc->write->ready = 0; + + return NGX_AGAIN; + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + + if (fin) { + r->qstream->out_closed = 1; + } + + r->qstream->headers_sent = 1; + + if (r->done) { + fc->write->handler = ngx_http_v3_close_stream_handler; + fc->read->handler = ngx_http_empty_handler; + } + + ngx_post_event(c->write, &ngx_posted_events); + + return NGX_OK; +} + + +static ssize_t +ngx_http_v3_stream_do_send(ngx_connection_t *fc, ngx_buf_t *b, ngx_int_t fin) +{ + ssize_t n; + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_v3_connection_t *h3c; + ngx_http_v3_stream_t *stream; + + uint8_t *buf = b ? b->pos : NULL; + size_t buf_len = b ? ngx_buf_size(b) : 0; + + r = fc->data; + stream = r->qstream; + h3c = stream->connection; + c = h3c->connection; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, fc->log, 0, + "http3 stream %uz to write %uz bytes, fin=%d", + stream->id, buf_len, fin); + + if (!stream->headers_sent) { + return NGX_AGAIN; + } + + n = quiche_h3_send_body(h3c->h3, c->quic->conn, r->qstream->id, + buf, buf_len, fin); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, stream->connection->connection->log, 0, + "http3 stream written %z bytes", n); + + if (n == QUICHE_H3_ERR_DONE) { + return NGX_AGAIN; + } + + if (n < 0) { + ngx_log_error(NGX_LOG_ERR, fc->log, 0, "stream write failed: %d", n); + return NGX_ERROR; + } + + return n; +} + + +static ssize_t +ngx_http_v3_recv_body(ngx_connection_t *c, u_char *buf, size_t size) +{ + ssize_t n; + ngx_event_t *rev; + ngx_http_request_t *r; + ngx_http_v3_connection_t *h3c; + + rev = c->read; + + r = c->data; + h3c = r->qstream->connection; + + if (c->error) { + rev->ready = 0; + + return NGX_ERROR; + } + + n = quiche_h3_recv_body(h3c->h3, c->quic->conn, r->qstream->id, buf, size); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "http3 body recv: %z of %uz", n, size); + + if (quiche_conn_stream_finished(c->quic->conn, r->qstream->id)) { + rev->ready = 0; + + /* Re-schedule connection read event to poll for Finished event. */ + ngx_post_event(h3c->connection->read, &ngx_posted_events); + } + + if (n == 0) { + rev->ready = 0; + + return 0; + } + + if (n > 0) { + + if ((size_t) n < size) { + rev->ready = 0; + } + + return n; + } + + if (n == QUICHE_H3_ERR_DONE) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quiche_h3_recv_body() not ready"); + + n = NGX_AGAIN; + + } else { + rev->error = 1; + + n = NGX_ERROR; + } + + rev->ready = 0; + + return n; +} + + +static ngx_chain_t * +ngx_http_v3_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) +{ + ssize_t n, sent; + off_t send, prev_send; + ngx_uint_t blocked, fin; + + ngx_http_request_t *r; + ngx_http_v3_stream_t *stream; + + r = fc->data; + stream = r->qstream; + + send = 0; + + blocked = 0; + + while (in) { + off_t size = ngx_buf_size(in->buf); + + if (size || in->buf->last_buf) { + break; + } + + in = in->next; + } + + if (in == NULL || stream->out_closed) { + return NULL; + } + + while (in) { + prev_send = send; + + fin = in->buf->last_buf; + + send += ngx_buf_size(in->buf); + + n = ngx_http_v3_stream_do_send(fc, in->buf, fin); + + if (n == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + sent = (n == NGX_AGAIN) ? 0 : n; + + fc->sent += sent; + + in->buf->pos += sent; + + /* Partial (or no) write, end now. */ + if ((n == NGX_AGAIN) || (send - prev_send != sent)) { + blocked = 1; + break; + } + + /* Buffer is fully written, switch to the next. */ + if (in->buf->pos == in->buf->last) { + in = in->next; + } + + if (fin) { + stream->out_closed = 1; + } + } + + if (blocked) { + if (!stream->blocked) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, stream->connection->connection->log, 0, + "http3 stream blocked %ui", stream->id); + + stream->blocked = 1; + + fc->write->active = 1; + fc->write->ready = 0; + } + } + + ngx_post_event(stream->connection->connection->write, &ngx_posted_events); + + return in; +} + + +void +ngx_http_v3_close_stream(ngx_http_v3_stream_t *stream, ngx_int_t rc) +{ + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_v3_connection_t *h3c; + + h3c = stream->connection; + + fc = stream->request->connection; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 close stream %ui", stream->id); + + if (stream->blocked) { + fc->write->handler = ngx_http_v3_close_stream_handler; + fc->read->handler = ngx_http_empty_handler; + return; + } + + quiche_conn_stream_shutdown(h3c->connection->quic->conn, stream->id, + QUICHE_SHUTDOWN_READ, 0); + + ngx_rbtree_delete(&h3c->streams, &stream->node); + + ngx_http_free_request(stream->request, rc); + + ev = fc->read; + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + ev = fc->write; + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + fc->data = h3c->free_fake_connections; + h3c->free_fake_connections = fc; + + h3c->processing--; + + ngx_http_v3_handle_connection(h3c); +} + + +static void +ngx_http_v3_close_stream_handler(ngx_event_t *ev) +{ + ngx_connection_t *fc; + ngx_http_request_t *r; + + fc = ev->data; + r = fc->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 close stream handler"); + + if (ev->timedout) { + ngx_log_error(NGX_LOG_INFO, fc->log, NGX_ETIMEDOUT, "client timed out"); + + fc->timedout = 1; + + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + ngx_http_v3_close_stream(r->qstream, 0); +} + +void +ngx_http_v3_stop_stream_read(ngx_http_v3_stream_t *stream, ngx_int_t rc) +{ + ngx_http_v3_connection_t *h3c; + + if (!stream) { + return; + } + + h3c = stream->connection; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 stream shutdown read %ui", stream->id); + + quiche_conn_stream_shutdown(h3c->connection->quic->conn, + stream->id, + QUICHE_SHUTDOWN_READ, rc); +} + + +static void +ngx_http_v3_finalize_connection(ngx_http_v3_connection_t *h3c, + ngx_uint_t status) +{ + ngx_event_t *ev; + ngx_connection_t *c, *fc; + ngx_rbtree_node_t *node, *root, *sentinel; + ngx_http_request_t *r; + ngx_http_v3_stream_t *stream; + + c = h3c->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 finalize connection"); + + quiche_conn_close(c->quic->conn, true, status, NULL, 0); + + c->error = 1; + + if (!h3c->processing) { + ngx_http_close_connection(c); + return; + } + + c->read->handler = ngx_http_empty_handler; + c->write->handler = ngx_http_empty_handler; + + root = h3c->streams.root; + sentinel = h3c->streams.sentinel; + + if (root != sentinel) { + node = ngx_rbtree_min(h3c->streams.root, sentinel); + } else { + node = NULL; + } + + /* Close all pending streams / requests. */ + while (node != NULL) { + stream = (ngx_http_v3_stream_t *) node; + + r = stream->request; + fc = r->connection; + + fc->error = 1; + + if (c->close) { + fc->close = 1; + } + + if (stream->blocked) { + stream->blocked = 0; + + ev = fc->write; + ev->active = 0; + ev->ready = 1; + + } else { + ev = fc->read; + } + + node = ngx_rbtree_next(&h3c->streams, node); + + ev->eof = 1; + ev->handler(ev); + } + + if (h3c->processing) { + return; + } + + ngx_http_close_connection(c); +} + + +static void +ngx_http_v3_pool_cleanup(void *data) +{ + ngx_http_v3_connection_t *h3c = data; + + if (h3c->h3) { + quiche_h3_conn_free(h3c->h3); + + h3c->h3 = NULL; + } +} diff --color -uNr a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c --- a/src/http/v3/ngx_http_v3_filter_module.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3_filter_module.c 2021-09-09 19:04:29.398062898 +0800 @@ -0,0 +1,68 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#include +#include +#include +#include + + +static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_v3_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_v3_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_filter_module = { + NGX_MODULE_V1, + &ngx_http_v3_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; + + +static ngx_int_t +ngx_http_v3_header_filter(ngx_http_request_t *r) +{ + if (!r->qstream) { + return ngx_http_next_header_filter(r); + } + + return ngx_http_v3_send_response(r); +} + + +static ngx_int_t +ngx_http_v3_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_v3_header_filter; + + return NGX_OK; +} diff --color -uNr a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h --- a/src/http/v3/ngx_http_v3.h 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3.h 2021-09-09 19:04:29.398062898 +0800 @@ -0,0 +1,79 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#ifndef _NGX_HTTP_V3_H_INCLUDED_ +#define _NGX_HTTP_V3_H_INCLUDED_ + + +#include +#include +#include +#include + + +#define NGX_HTTP_V3_ALPN_ADVERTISE "\x05h3-18" + + +typedef struct ngx_http_v3_connection_s ngx_http_v3_connection_t; + + +struct ngx_http_v3_connection_s { + quiche_h3_conn *h3; + + ngx_connection_t *connection; + ngx_http_connection_t *http_connection; + + ngx_pool_t *pool; + + ngx_uint_t processing; + + ngx_rbtree_t streams; + ngx_rbtree_node_t streams_sentinel; + + ngx_connection_t *free_fake_connections; +}; + + +struct ngx_http_v3_stream_s { + ngx_rbtree_node_t node; + + uint64_t id; + + ngx_http_request_t *request; + + ngx_http_v3_connection_t *connection; + + ngx_array_t *headers; + ngx_array_t *cookies; + + ngx_http_v3_stream_t *next; + + ngx_uint_t headers_sent:1; + ngx_uint_t in_closed:1; + ngx_uint_t out_closed:1; + ngx_uint_t skip_data:1; + ngx_uint_t blocked:1; +}; + + +typedef struct { + ngx_str_t name; + ngx_str_t value; +} ngx_http_v3_header_t; + + +void ngx_http_v3_init(ngx_event_t *rev); + +ngx_int_t ngx_http_v3_send_response(ngx_http_request_t *r); + +void ngx_http_v3_close_stream(ngx_http_v3_stream_t *stream, ngx_int_t rc); +void ngx_http_v3_stop_stream_read(ngx_http_v3_stream_t *stream, ngx_int_t rc); + +ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r, + ngx_chain_t *in); + + +#endif /* _NGX_HTTP_V3_H_INCLUDED_ */ diff --color -uNr a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c --- a/src/http/v3/ngx_http_v3_module.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3_module.c 2021-09-09 19:04:29.398062898 +0800 @@ -0,0 +1,286 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#include +#include +#include +#include + +#include + + +static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); + +static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, + void *parent, void *child); + +static ngx_int_t ngx_http_v3_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static void ngx_http_v3_cleanup_ctx(void *data); + + +static ngx_command_t ngx_http_v3_commands[] = { + + { ngx_string("http3_max_concurrent_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, concurrent_streams), + NULL }, + + { ngx_string("http3_max_requests"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_requests), + NULL }, + + { ngx_string("http3_max_header_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_header_size), + NULL }, + + { ngx_string("http3_initial_max_data"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_data), + NULL }, + + { ngx_string("http3_initial_max_stream_data"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_stream_data), + NULL }, + + { ngx_string("http3_idle_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, idle_timeout), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_v3_module_ctx = { + ngx_http_v3_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_http_v3_create_srv_conf, /* create server configuration */ + ngx_http_v3_merge_srv_conf, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_module = { + NGX_MODULE_V1, + &ngx_http_v3_module_ctx, /* module context */ + ngx_http_v3_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_variable_t ngx_http_v3_variables[] = { + + { ngx_string("http3"), NULL, + ngx_http_v3_variable, 0, + NGX_HTTP_VAR_CHANGEABLE, 0 }, + + ngx_http_null_variable +}; + + +static ngx_int_t +ngx_http_v3_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_v3_variables; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static void * +ngx_http_v3_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_v3_srv_conf_t *h3scf; + + h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t)); + if (h3scf == NULL) { + return NULL; + } + + h3scf->idle_timeout = NGX_CONF_UNSET_MSEC; + h3scf->max_data = NGX_CONF_UNSET_SIZE; + h3scf->max_stream_data = NGX_CONF_UNSET_SIZE; + h3scf->max_requests = NGX_CONF_UNSET_UINT; + h3scf->max_header_size = NGX_CONF_UNSET_SIZE; + h3scf->concurrent_streams = NGX_CONF_UNSET_UINT; + + return h3scf; +} + + +#if (NGX_DEBUG) +static void +quiche_log(const char *line, void *argp) +{ + ngx_log_t *log = ngx_cycle->log; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "%s", line); +} +#endif + + +static char * +ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_v3_srv_conf_t *prev = parent; + ngx_http_v3_srv_conf_t *conf = child; + + ngx_pool_cleanup_t *cln; + + ngx_conf_merge_msec_value(conf->idle_timeout, + prev->idle_timeout, 180000); + + ngx_conf_merge_size_value(conf->max_data, + prev->max_data, 10485760); + + ngx_conf_merge_size_value(conf->max_stream_data, + prev->max_stream_data, 1048576); + + ngx_conf_merge_uint_value(conf->max_requests, + prev->max_requests, 1000); + + ngx_conf_merge_size_value(conf->max_header_size, + prev->max_header_size, 16384); + + ngx_conf_merge_uint_value(conf->concurrent_streams, + prev->concurrent_streams, 128); + + conf->quic.log = cf->log; + +#if (NGX_DEBUG) + /* Enable quiche debug logging. quiche commit ceade4 or later is required */ + quiche_enable_debug_logging(quiche_log, NULL); +#endif + + if (ngx_quic_create_conf(&conf->quic) != NGX_OK) { + return NGX_CONF_ERROR; + } + + quiche_config_set_max_idle_timeout(conf->quic.config, conf->idle_timeout); + + quiche_config_set_initial_max_data(conf->quic.config, conf->max_data); + + quiche_config_set_initial_max_stream_data_bidi_remote(conf->quic.config, + conf->max_stream_data); + + quiche_config_set_initial_max_stream_data_uni(conf->quic.config, + conf->max_stream_data); + + quiche_config_set_initial_max_streams_bidi(conf->quic.config, + conf->concurrent_streams); + + /* For HTTP/3 we only need 3 unidirectional streams. */ + quiche_config_set_initial_max_streams_uni(conf->quic.config, 3); + + conf->http3 = quiche_h3_config_new(); + if (conf->http3 == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "failed to create HTTP/3 config"); + return NGX_CONF_ERROR; + } + + quiche_h3_config_set_max_field_section_size(conf->http3, + conf->max_header_size); + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NGX_CONF_ERROR; + } + + cln->handler = ngx_quic_cleanup_ctx; + cln->data = &conf->quic; + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NGX_CONF_ERROR; + } + + cln->handler = ngx_http_v3_cleanup_ctx; + cln->data = conf->http3; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_v3_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, + uintptr_t data) +{ + ngx_connection_t *c; + + v->valid = 1; + v->no_cacheable = 1; + v->not_found = 0; + + c = r->connection; + if (c == NULL) { + return NGX_ERROR; + } + + if (c->quic != NULL) { + v->len = sizeof("h3") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "h3"; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + return NGX_OK; +} + + +static void +ngx_http_v3_cleanup_ctx(void *data) +{ + quiche_h3_config *config = data; + + quiche_h3_config_free(config); +} diff --color -uNr a/src/http/v3/ngx_http_v3_module.h b/src/http/v3/ngx_http_v3_module.h --- a/src/http/v3/ngx_http_v3_module.h 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3_module.h 2021-09-09 19:04:29.398062898 +0800 @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#ifndef _NGX_HTTP_V3_MODULE_H_INCLUDED_ +#define _NGX_HTTP_V3_MODULE_H_INCLUDED_ + + +#include +#include + +#include + + +typedef struct { + ngx_quic_t quic; + + quiche_h3_config *http3; + + ngx_msec_t idle_timeout; + size_t max_data; + size_t max_stream_data; + ngx_uint_t max_requests; + ngx_uint_t max_header_size; + ngx_uint_t concurrent_streams; +} ngx_http_v3_srv_conf_t; + + +extern ngx_module_t ngx_http_v3_module; + + +#endif /* _NGX_HTTP_V3_MODULE_H_INCLUDED_ */ diff --color -uNr a/src/os/unix/ngx_udp_sendmsg_chain.c b/src/os/unix/ngx_udp_sendmsg_chain.c --- a/src/os/unix/ngx_udp_sendmsg_chain.c 2021-09-07 23:21:03.000000000 +0800 +++ b/src/os/unix/ngx_udp_sendmsg_chain.c 2021-09-09 19:04:29.398062898 +0800 @@ -322,6 +322,7 @@ switch (err) { case NGX_EAGAIN: + case ENOBUFS: ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, "sendmsg() not ready"); return NGX_AGAIN; ================================================ FILE: nginx_with_spdy.patch ================================================ Add SPDY Support. Add HTTP2 HPACK Encoding Support. Add Dynamic TLS Record support. Using: patch -p1 < nginx.patch diff -uNr a/auto/modules b/auto/modules --- a/auto/modules 2019-09-24 23:08:48.000000000 +0800 +++ b/auto/modules 2019-10-17 22:51:22.603255482 +0800 @@ -119,6 +119,7 @@ # ngx_http_header_filter # ngx_http_chunked_filter # ngx_http_v2_filter + # ngx_http_spdy_filter # ngx_http_range_header_filter # ngx_http_gzip_filter # ngx_http_postpone_filter @@ -151,6 +152,7 @@ ngx_http_header_filter_module \ ngx_http_chunked_filter_module \ ngx_http_v2_filter_module \ + ngx_http_spdy_filter_module \ ngx_http_range_header_filter_module \ ngx_http_gzip_filter_module \ ngx_http_postpone_filter_module \ @@ -212,6 +214,19 @@ . auto/module fi + if [ $HTTP_SPDY = YES ]; then + have=NGX_HTTP_SPDY . auto/have + USE_ZLIB=YES + ngx_module_name=ngx_http_spdy_filter_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs=src/http/ngx_http_spdy_filter_module.c + ngx_module_libs= + ngx_module_link=$HTTP_SPDY + + . auto/module + fi + if :; then ngx_module_name=ngx_http_range_header_filter_module ngx_module_incs= @@ -422,6 +437,23 @@ . auto/module fi + + if [ $HTTP_V2_HPACK_ENC = YES ]; then + have=NGX_HTTP_V2_HPACK_ENC . auto/have + fi + + if [ $HTTP_SPDY = YES ]; then + have=NGX_HTTP_SPDY . auto/have + ngx_module_name=ngx_http_spdy_module + ngx_module_incs=src/http + ngx_module_deps="src/http/ngx_http_spdy.h src/http/ngx_http_spdy_module.h" + ngx_module_srcs="src/http/ngx_http_spdy.c \ + src/http/ngx_http_spdy_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_SPDY + + . auto/module + fi if :; then ngx_module_name=ngx_http_static_module diff -uNr a/auto/options b/auto/options --- a/auto/options 2019-09-24 23:08:48.000000000 +0800 +++ b/auto/options 2019-10-17 22:51:22.603255482 +0800 @@ -58,7 +58,9 @@ HTTP_CHARSET=YES HTTP_GZIP=YES HTTP_SSL=NO +HTTP_SPDY=NO HTTP_V2=NO +HTTP_V2_HPACK_ENC=NO HTTP_SSI=YES HTTP_REALIP=NO HTTP_XSLT=NO @@ -223,7 +225,9 @@ --http-scgi-temp-path=*) NGX_HTTP_SCGI_TEMP_PATH="$value" ;; --with-http_ssl_module) HTTP_SSL=YES ;; + --with-http_spdy_module) HTTP_SPDY=YES ;; --with-http_v2_module) HTTP_V2=YES ;; + --with-http_v2_hpack_enc) HTTP_V2_HPACK_ENC=YES ;; --with-http_realip_module) HTTP_REALIP=YES ;; --with-http_addition_module) HTTP_ADDITION=YES ;; --with-http_xslt_module) HTTP_XSLT=YES ;; @@ -438,7 +442,9 @@ --with-file-aio enable file AIO support --with-http_ssl_module enable ngx_http_ssl_module + --with-http_spdy_module enable ngx_http_spdy_module --with-http_v2_module enable ngx_http_v2_module + --with-http_v2_hpack_enc enable ngx_http_v2_hpack_enc --with-http_realip_module enable ngx_http_realip_module --with-http_addition_module enable ngx_http_addition_module --with-http_xslt_module enable ngx_http_xslt_module diff -uNr a/src/core/ngx_connection.h b/src/core/ngx_connection.h --- a/src/core/ngx_connection.h 2019-09-24 23:08:48.000000000 +0800 +++ b/src/core/ngx_connection.h 2019-10-17 22:51:22.604255490 +0800 @@ -119,6 +119,7 @@ #define NGX_LOWLEVEL_BUFFERED 0x0f #define NGX_SSL_BUFFERED 0x01 #define NGX_HTTP_V2_BUFFERED 0x02 +#define NGX_SPDY_BUFFERED 0x04 struct ngx_connection_s { diff -uNr a/src/core/ngx_murmurhash.c b/src/core/ngx_murmurhash.c --- a/src/core/ngx_murmurhash.c 2019-09-24 23:08:48.000000000 +0800 +++ b/src/core/ngx_murmurhash.c 2019-10-17 22:51:22.604255490 +0800 @@ -50,3 +50,63 @@ return h; } + + +uint64_t +ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed) +{ + uint64_t h, k; + + h = seed ^ len; + + while (len >= 8) { + k = data[0]; + k |= data[1] << 8; + k |= data[2] << 16; + k |= data[3] << 24; + k |= (uint64_t)data[4] << 32; + k |= (uint64_t)data[5] << 40; + k |= (uint64_t)data[6] << 48; + k |= (uint64_t)data[7] << 56; + + k *= 0xc6a4a7935bd1e995ull; + k ^= k >> 47; + k *= 0xc6a4a7935bd1e995ull; + + h ^= k; + h *= 0xc6a4a7935bd1e995ull; + + data += 8; + len -= 8; + } + + switch (len) { + case 7: + h ^= (uint64_t)data[6] << 48; + /* fall through */ + case 6: + h ^= (uint64_t)data[5] << 40; + /* fall through */ + case 5: + h ^= (uint64_t)data[4] << 32; + /* fall through */ + case 4: + h ^= data[3] << 24; + /* fall through */ + case 3: + h ^= data[2] << 16; + /* fall through */ + case 2: + h ^= data[1] << 8; + /* fall through */ + case 1: + h ^= data[0]; + h *= 0xc6a4a7935bd1e995ull; + } + + h ^= h >> 47; + h *= 0xc6a4a7935bd1e995ull; + h ^= h >> 47; + + return h; +} diff -uNr a/src/core/ngx_murmurhash.h b/src/core/ngx_murmurhash.h --- a/src/core/ngx_murmurhash.h 2019-09-24 23:08:48.000000000 +0800 +++ b/src/core/ngx_murmurhash.h 2019-10-17 22:51:22.605255498 +0800 @@ -15,5 +15,7 @@ uint32_t ngx_murmur_hash2(u_char *data, size_t len); +uint64_t ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed); + #endif /* _NGX_MURMURHASH_H_INCLUDED_ */ diff -uNr a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c 2019-09-24 23:08:48.000000000 +0800 +++ b/src/event/ngx_event_openssl.c 2019-10-17 22:51:22.607255514 +0800 @@ -1507,6 +1507,7 @@ sc->buffer = ((flags & NGX_SSL_BUFFER) != 0); sc->buffer_size = ssl->buffer_size; + sc->dyn_rec = ssl->dyn_rec; sc->session_ctx = ssl->ctx; @@ -2359,6 +2360,41 @@ for ( ;; ) { + /* Dynamic record resizing: + We want the initial records to fit into one TCP segment + so we don't get TCP HoL blocking due to TCP Slow Start. + A connection always starts with small records, but after + a given amount of records sent, we make the records larger + to reduce header overhead. + After a connection has idled for a given timeout, begin + the process from the start. The actual parameters are + configurable. If dyn_rec_timeout is 0, we assume dyn_rec is off. */ + + if (c->ssl->dyn_rec.timeout > 0 ) { + + if (ngx_current_msec - c->ssl->dyn_rec_last_write > + c->ssl->dyn_rec.timeout) + { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + c->ssl->dyn_rec_records_sent = 0; + + } else { + if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold * 2) + { + buf->end = buf->start + c->ssl->buffer_size; + + } else if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold) + { + buf->end = buf->start + c->ssl->dyn_rec.size_hi; + + } else { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + } + } + } + while (in && buf->last < buf->end && send < limit) { if (in->buf->last_buf || in->buf->flush) { flush = 1; @@ -2466,6 +2502,9 @@ if (n > 0) { + c->ssl->dyn_rec_records_sent++; + c->ssl->dyn_rec_last_write = ngx_current_msec; + if (c->ssl->saved_read_handler) { c->read->handler = c->ssl->saved_read_handler; diff -uNr a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h 2019-09-24 23:08:48.000000000 +0800 +++ b/src/event/ngx_event_openssl.h 2019-10-17 22:51:22.607255514 +0800 @@ -64,10 +64,19 @@ #endif +typedef struct { + ngx_msec_t timeout; + ngx_uint_t threshold; + size_t size_lo; + size_t size_hi; +} ngx_ssl_dyn_rec_t; + + struct ngx_ssl_s { SSL_CTX *ctx; ngx_log_t *log; size_t buffer_size; + ngx_ssl_dyn_rec_t dyn_rec; }; @@ -99,6 +108,10 @@ unsigned in_early:1; unsigned early_preread:1; unsigned write_blocked:1; + + ngx_ssl_dyn_rec_t dyn_rec; + ngx_msec_t dyn_rec_last_write; + ngx_uint_t dyn_rec_records_sent; }; @@ -108,7 +121,7 @@ #define NGX_SSL_DFLT_BUILTIN_SCACHE -5 -#define NGX_SSL_MAX_SESSION_SIZE 4096 +#define NGX_SSL_MAX_SESSION_SIZE 16384 typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t; diff -uNr a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.c 2019-10-17 22:51:22.608255522 +0800 @@ -249,6 +249,41 @@ offsetof(ngx_http_ssl_srv_conf_t, early_data), NULL }, + { ngx_string("ssl_dyn_rec_enable"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_enable), + NULL }, + + { ngx_string("ssl_dyn_rec_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_timeout), + NULL }, + + { ngx_string("ssl_dyn_rec_size_lo"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_lo), + NULL }, + + { ngx_string("ssl_dyn_rec_size_hi"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_hi), + NULL }, + + { ngx_string("ssl_dyn_rec_threshold"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_threshold), + NULL }, + ngx_null_command }; @@ -371,10 +406,10 @@ #if (NGX_DEBUG) unsigned int i; #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY) ngx_http_connection_t *hc; #endif -#if (NGX_HTTP_V2 || NGX_DEBUG) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_DEBUG) ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); @@ -388,9 +423,20 @@ } #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY) hc = c->data; +#endif + +#if (NGX_HTTP_V2 && NGX_HTTP_SPDY) + if (hc->addr_conf->http2 && hc->addr_conf->spdy) { + srv = (unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE + NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; + srvlen = sizeof(NGX_HTTP_V2_ALPN_ADVERTISE NGX_SPDY_NPN_ADVERTISE + NGX_HTTP_NPN_ADVERTISE) - 1; + } else +#endif +#if (NGX_HTTP_V2) if (hc->addr_conf->http2) { srv = (unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; @@ -398,6 +444,13 @@ } else #endif +#if (NGX_HTTP_SPDY) + if (hc->addr_conf->spdy) { + srv = (unsigned char *) NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; + srvlen = sizeof(NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1; + + } else +#endif { srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE; srvlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1; @@ -425,19 +478,32 @@ ngx_http_ssl_npn_advertised(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, unsigned int *outlen, void *arg) { -#if (NGX_HTTP_V2 || NGX_DEBUG) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_DEBUG) ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "SSL NPN advertised"); #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY) { ngx_http_connection_t *hc; hc = c->data; +#endif + +#if (NGX_HTTP_V2 && NGX_HTTP_SPDY) + if (hc->addr_conf->http2 && hc->addr_conf->spdy) { + *out = (unsigned char *) NGX_HTTP_V2_NPN_ADVERTISE + NGX_SPDY_NPN_ADVERTISE + NGX_HTTP_NPN_ADVERTISE; + *outlen = sizeof(NGX_HTTP_V2_NPN_ADVERTISE NGX_SPDY_NPN_ADVERTISE + NGX_HTTP_NPN_ADVERTISE) - 1; + return SSL_TLSEXT_ERR_OK; + } else +#endif +#if (NGX_HTTP_V2) if (hc->addr_conf->http2) { *out = (unsigned char *) NGX_HTTP_V2_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; @@ -445,6 +511,20 @@ return SSL_TLSEXT_ERR_OK; } +#endif +#if (NGX_HTTP_V2 && NGX_HTTP_SPDY) + else +#endif +#if (NGX_HTTP_SPDY) + if (hc->addr_conf->spdy) { + *out = (unsigned char *) NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; + *outlen = sizeof(NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1; + + return SSL_TLSEXT_ERR_OK; + } +#endif + +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY) } #endif @@ -580,6 +660,11 @@ sscf->session_ticket_keys = NGX_CONF_UNSET_PTR; sscf->stapling = NGX_CONF_UNSET; sscf->stapling_verify = NGX_CONF_UNSET; + sscf->dyn_rec_enable = NGX_CONF_UNSET; + sscf->dyn_rec_timeout = NGX_CONF_UNSET_MSEC; + sscf->dyn_rec_size_lo = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_size_hi = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_threshold = NGX_CONF_UNSET_UINT; return sscf; } @@ -647,6 +732,20 @@ ngx_conf_merge_str_value(conf->stapling_responder, prev->stapling_responder, ""); + ngx_conf_merge_value(conf->dyn_rec_enable, prev->dyn_rec_enable, 0); + ngx_conf_merge_msec_value(conf->dyn_rec_timeout, prev->dyn_rec_timeout, + 1000); + /* Default sizes for the dynamic record sizes are defined to fit maximal + TLS + IPv6 overhead in a single TCP segment for lo and 3 segments for hi: + 1369 = 1500 - 40 (IP) - 20 (TCP) - 10 (Time) - 61 (Max TLS overhead) */ + ngx_conf_merge_size_value(conf->dyn_rec_size_lo, prev->dyn_rec_size_lo, + 1369); + /* 4229 = (1500 - 40 - 20 - 10) * 3 - 61 */ + ngx_conf_merge_size_value(conf->dyn_rec_size_hi, prev->dyn_rec_size_hi, + 4229); + ngx_conf_merge_uint_value(conf->dyn_rec_threshold, prev->dyn_rec_threshold, + 40); + conf->ssl.log = cf->log; if (conf->enable) { @@ -857,6 +956,28 @@ return NGX_CONF_ERROR; } + if (conf->dyn_rec_enable) { + conf->ssl.dyn_rec.timeout = conf->dyn_rec_timeout; + conf->ssl.dyn_rec.threshold = conf->dyn_rec_threshold; + + if (conf->buffer_size > conf->dyn_rec_size_lo) { + conf->ssl.dyn_rec.size_lo = conf->dyn_rec_size_lo; + + } else { + conf->ssl.dyn_rec.size_lo = conf->buffer_size; + } + + if (conf->buffer_size > conf->dyn_rec_size_hi) { + conf->ssl.dyn_rec.size_hi = conf->dyn_rec_size_hi; + + } else { + conf->ssl.dyn_rec.size_hi = conf->buffer_size; + } + + } else { + conf->ssl.dyn_rec.timeout = 0; + } + return NGX_CONF_OK; } diff -uNr a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.h 2019-10-17 22:51:22.609255531 +0800 @@ -61,6 +61,12 @@ u_char *file; ngx_uint_t line; + + ngx_flag_t dyn_rec_enable; + ngx_msec_t dyn_rec_timeout; + size_t dyn_rec_size_lo; + size_t dyn_rec_size_hi; + ngx_uint_t dyn_rec_threshold; } ngx_http_ssl_srv_conf_t; diff -uNr a/src/http/ngx_http.c b/src/http/ngx_http.c --- a/src/http/ngx_http.c 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/ngx_http.c 2019-10-17 22:51:22.610255539 +0800 @@ -1199,6 +1199,9 @@ #if (NGX_HTTP_V2) ngx_uint_t http2; #endif +#if (NGX_HTTP_SPDY) + ngx_uint_t spdy; +#endif /* * we cannot compare whole sockaddr struct's as kernel @@ -1234,6 +1237,9 @@ #if (NGX_HTTP_V2) http2 = lsopt->http2 || addr[i].opt.http2; #endif +#if (NGX_HTTP_SPDY) + spdy = lsopt->spdy || addr[i].opt.spdy; +#endif if (lsopt->set) { @@ -1270,6 +1276,9 @@ #if (NGX_HTTP_V2) addr[i].opt.http2 = http2; #endif +#if (NGX_HTTP_SPDY) + addr[i].opt.spdy = spdy; +#endif return NGX_OK; } @@ -1313,6 +1322,18 @@ #endif +#if (NGX_HTTP_SPDY && NGX_HTTP_SSL \ + && !defined TLSEXT_TYPE_application_layer_protocol_negotiation \ + && !defined TLSEXT_TYPE_next_proto_neg) + if (lsopt->spdy && lsopt->ssl) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "nginx was built with OpenSSL that lacks ALPN " + "and NPN support, SPDY is not enabled for %s", + lsopt->addr); + } + +#endif + addr = ngx_array_push(&port->addrs); if (addr == NULL) { return NGX_ERROR; @@ -1802,6 +1823,9 @@ #if (NGX_HTTP_V2) addrs[i].conf.http2 = addr[i].opt.http2; #endif +#if (NGX_HTTP_SPDY) + addrs[i].conf.spdy = addr[i].opt.spdy; +#endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; if (addr[i].hash.buckets == NULL @@ -1867,6 +1891,9 @@ #if (NGX_HTTP_V2) addrs6[i].conf.http2 = addr[i].opt.http2; #endif +#if (NGX_HTTP_SPDY) + addrs6[i].conf.spdy = addr[i].opt.spdy; +#endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; if (addr[i].hash.buckets == NULL diff -uNr a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/ngx_http_core_module.c 2019-10-17 22:51:22.612255555 +0800 @@ -1938,6 +1938,13 @@ return NGX_DECLINED; } +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + r->gzip_ok = 1; + return NGX_OK; + } +#endif + ae = r->headers_in.accept_encoding; if (ae == NULL) { return NGX_DECLINED; @@ -2295,6 +2302,9 @@ #if (NGX_HTTP_V2) sr->stream = r->stream; #endif +#if (NGX_HTTP_SPDY) + sr->spdy_stream = r->spdy_stream; +#endif sr->method = NGX_HTTP_GET; sr->http_version = r->http_version; @@ -4000,11 +4010,15 @@ } if (ngx_strcmp(value[n].data, "spdy") == 0) { - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "invalid parameter \"spdy\": " - "ngx_http_spdy_module was superseded " - "by ngx_http_v2_module"); +#if (NGX_HTTP_SPDY) + lsopt.spdy = 1; continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"spdy\" parameter requires " + "ngx_http_spdy_module"); + return NGX_CONF_ERROR; +#endif } if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) { diff -uNr a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h --- a/src/http/ngx_http_core_module.h 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/ngx_http_core_module.h 2019-10-17 22:51:22.613255563 +0800 @@ -75,6 +75,9 @@ unsigned wildcard:1; unsigned ssl:1; unsigned http2:1; +#if (NGX_HTTP_SPDY) + unsigned spdy:1; +#endif #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif @@ -237,6 +240,9 @@ unsigned ssl:1; unsigned http2:1; +#if (NGX_HTTP_SPDY) + unsigned spdy:1; +#endif unsigned proxy_protocol:1; }; diff -uNr a/src/http/ngx_http.h b/src/http/ngx_http.h --- a/src/http/ngx_http.h 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/ngx_http.h 2019-10-17 22:51:22.613255563 +0800 @@ -19,6 +19,9 @@ typedef struct ngx_http_file_cache_s ngx_http_file_cache_t; typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t; typedef struct ngx_http_chunked_s ngx_http_chunked_t; +#if (NGX_HTTP_SPDY) +typedef struct ngx_http_spdy_stream_s ngx_http_spdy_stream_t; +#endif typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t; typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, @@ -35,9 +38,13 @@ #include #include + #if (NGX_HTTP_V2) #include #endif +#if (NGX_HTTP_SPDY) +#include +#endif #if (NGX_HTTP_CACHE) #include #endif diff -uNr a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c --- a/src/http/ngx_http_request_body.c 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/ngx_http_request_body.c 2019-10-17 22:51:22.614255571 +0800 @@ -84,6 +84,12 @@ goto done; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + rc = ngx_http_spdy_read_request_body(r, post_handler); + goto done; + } +#endif preread = r->header_in->last - r->header_in->pos; @@ -524,6 +530,12 @@ return NGX_OK; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + r->spdy_stream->skip_data = 1; + return NGX_OK; + } +#endif if (ngx_http_test_expect(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; diff -uNr a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/ngx_http_request.c 2019-10-17 22:51:22.615255580 +0800 @@ -324,6 +324,11 @@ rev->handler = ngx_http_wait_request_handler; c->write->handler = ngx_http_empty_handler; +#if (NGX_HTTP_SPDY) + if (hc->addr_conf->spdy) { + rev->handler = ngx_http_spdy_init; + } +#endif #if (NGX_HTTP_V2) if (hc->addr_conf->http2) { rev->handler = ngx_http_v2_init; @@ -830,6 +835,34 @@ } #endif +#if (NGX_HTTP_SPDY \ + && (defined TLSEXT_TYPE_application_layer_protocol_negotiation \ + || defined TLSEXT_TYPE_next_proto_neg)) + { + unsigned int len; + const unsigned char *data; + static const ngx_str_t spdy = ngx_string(NGX_SPDY_NPN_NEGOTIATED); + +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + +#ifdef TLSEXT_TYPE_next_proto_neg + if (len == 0) { + SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len); + } +#endif + +#else /* TLSEXT_TYPE_next_proto_neg */ + SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len); +#endif + + if (len == spdy.len && ngx_strncmp(data, spdy.data, spdy.len) == 0) { + ngx_http_spdy_init(c->read); + return; + } + } +#endif + c->log->action = "waiting for request"; c->read->handler = ngx_http_wait_request_handler; @@ -2686,6 +2719,12 @@ return; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + ngx_http_close_request(r, 0); + return; + } +#endif clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); @@ -2895,6 +2934,18 @@ } #endif +#if (NGX_HTTP_SPDY) + + if (r->spdy_stream) { + if (c->error) { + err = 0; + goto closed; + } + + return; + } + +#endif #if (NGX_HAVE_KQUEUE) @@ -3562,6 +3613,12 @@ return; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + ngx_http_spdy_close_stream(r->spdy_stream, rc); + return; + } +#endif ngx_http_free_request(r, rc); ngx_http_close_connection(c); diff -uNr a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/ngx_http_request.h 2019-10-17 22:51:22.616255588 +0800 @@ -432,6 +432,9 @@ int *captures; u_char *captures_data; #endif +#if (NGX_HTTP_SPDY) + ngx_http_spdy_stream_t *spdy_stream; +#endif size_t limit_rate; size_t limit_rate_after; diff -uNr a/src/http/ngx_http_spdy.c b/src/http/ngx_http_spdy.c --- a/src/http/ngx_http_spdy.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/ngx_http_spdy.c 2019-10-17 22:51:22.619255612 +0800 @@ -0,0 +1,3701 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include + +#include + + +#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED) + +#define ngx_str5cmp(m, c0, c1, c2, c3, c4) \ + *(uint32_t *) m == (c3 << 24 | c2 << 16 | c1 << 8 | c0) \ + && m[4] == c4 + +#else + +#define ngx_str5cmp(m, c0, c1, c2, c3, c4) \ + m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 && m[4] == c4 + +#endif + + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_spdy_frame_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_spdy_frame_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#else + +#define ngx_spdy_frame_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_spdy_frame_parse_uint32(p) \ + ((p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#endif + +#define ngx_spdy_frame_parse_sid(p) \ + (ngx_spdy_frame_parse_uint32(p) & 0x7fffffff) +#define ngx_spdy_frame_parse_delta(p) \ + (ngx_spdy_frame_parse_uint32(p) & 0x7fffffff) + + +#define ngx_spdy_ctl_frame_check(h) \ + (((h) & 0xffff0000) == ngx_spdy_ctl_frame_head(0)) +#define ngx_spdy_data_frame_check(h) \ + (!((h) & (uint32_t) NGX_SPDY_CTL_BIT << 31)) + +#define ngx_spdy_ctl_frame_type(h) ((h) & 0x0000ffff) +#define ngx_spdy_frame_flags(p) ((p) >> 24) +#define ngx_spdy_frame_length(p) ((p) & 0x00ffffff) +#define ngx_spdy_frame_id(p) ((p) & 0x00ffffff) + + +#define NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE 4096 +#define NGX_SPDY_CTL_FRAME_BUFFER_SIZE 16 + +#define NGX_SPDY_PROTOCOL_ERROR 1 +#define NGX_SPDY_INVALID_STREAM 2 +#define NGX_SPDY_REFUSED_STREAM 3 +#define NGX_SPDY_UNSUPPORTED_VERSION 4 +#define NGX_SPDY_CANCEL 5 +#define NGX_SPDY_INTERNAL_ERROR 6 +#define NGX_SPDY_FLOW_CONTROL_ERROR 7 +#define NGX_SPDY_STREAM_IN_USE 8 +#define NGX_SPDY_STREAM_ALREADY_CLOSED 9 +/* deprecated 10 */ +#define NGX_SPDY_FRAME_TOO_LARGE 11 + +#define NGX_SPDY_SETTINGS_MAX_STREAMS 4 +#define NGX_SPDY_SETTINGS_INIT_WINDOW 7 + +#define NGX_SPDY_SETTINGS_FLAG_PERSIST 0x01 +#define NGX_SPDY_SETTINGS_FLAG_PERSISTED 0x02 + +#define NGX_SPDY_MAX_WINDOW NGX_MAX_INT32_VALUE +#define NGX_SPDY_CONNECTION_WINDOW 65536 +#define NGX_SPDY_INIT_STREAM_WINDOW 65536 +#define NGX_SPDY_STREAM_WINDOW NGX_SPDY_MAX_WINDOW + +typedef struct { + ngx_uint_t hash; + u_char len; + u_char header[7]; + ngx_int_t (*handler)(ngx_http_request_t *r); +} ngx_http_spdy_request_header_t; + + +static void ngx_http_spdy_read_handler(ngx_event_t *rev); +static void ngx_http_spdy_write_handler(ngx_event_t *wev); +static void ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc); + +static u_char *ngx_http_spdy_proxy_protocol(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_head(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_syn_stream(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_headers(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_headers_skip(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_headers_error(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_window_update(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_data(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_read_data(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_rst_stream(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_ping(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_skip(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_settings(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_complete(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_save(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end, ngx_http_spdy_handler_pt handler); + +static u_char *ngx_http_spdy_state_inflate_error( + ngx_http_spdy_connection_t *sc, int rc); +static u_char *ngx_http_spdy_state_protocol_error( + ngx_http_spdy_connection_t *sc); +static u_char *ngx_http_spdy_state_internal_error( + ngx_http_spdy_connection_t *sc); + +static ngx_int_t ngx_http_spdy_send_window_update( + ngx_http_spdy_connection_t *sc, ngx_uint_t sid, ngx_uint_t delta); +static ngx_int_t ngx_http_spdy_send_rst_stream(ngx_http_spdy_connection_t *sc, + ngx_uint_t sid, ngx_uint_t status, ngx_uint_t priority); +static ngx_int_t ngx_http_spdy_send_settings(ngx_http_spdy_connection_t *sc); +static ngx_int_t ngx_http_spdy_settings_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); +static ngx_http_spdy_out_frame_t *ngx_http_spdy_get_ctl_frame( + ngx_http_spdy_connection_t *sc, size_t size, ngx_uint_t priority); +static ngx_int_t ngx_http_spdy_ctl_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); + +static ngx_http_spdy_stream_t *ngx_http_spdy_create_stream( + ngx_http_spdy_connection_t *sc, ngx_uint_t id, ngx_uint_t priority); +static ngx_http_spdy_stream_t *ngx_http_spdy_get_stream_by_id( + ngx_http_spdy_connection_t *sc, ngx_uint_t sid); +#define ngx_http_spdy_streams_index_size(sscf) (sscf->streams_index_mask + 1) +#define ngx_http_spdy_stream_index(sscf, sid) \ + ((sid >> 1) & sscf->streams_index_mask) + +static ngx_int_t ngx_http_spdy_parse_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_alloc_large_header_buffer(ngx_http_request_t *r); + +static ngx_int_t ngx_http_spdy_handle_request_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_method(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_scheme(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_host(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_path(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_version(ngx_http_request_t *r); + +static ngx_int_t ngx_http_spdy_construct_request_line(ngx_http_request_t *r); +static void ngx_http_spdy_run_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_init_request_body(ngx_http_request_t *r); + +static ngx_int_t ngx_http_spdy_terminate_stream(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream, ngx_uint_t status); + +static void ngx_http_spdy_close_stream_handler(ngx_event_t *ev); + +static void ngx_http_spdy_handle_connection_handler(ngx_event_t *rev); +static void ngx_http_spdy_keepalive_handler(ngx_event_t *rev); +static void ngx_http_spdy_finalize_connection(ngx_http_spdy_connection_t *sc, + ngx_int_t rc); + +static ngx_int_t ngx_http_spdy_adjust_windows(ngx_http_spdy_connection_t *sc, + ssize_t delta); + +static void ngx_http_spdy_pool_cleanup(void *data); + +static void *ngx_http_spdy_zalloc(void *opaque, u_int items, u_int size); +static void ngx_http_spdy_zfree(void *opaque, void *address); + + +static const u_char ngx_http_spdy_dict[] = { + 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, /* - - - - o p t i */ + 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, /* o n s - - - - h */ + 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, /* e a d - - - - p */ + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, /* o s t - - - - p */ + 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, /* u t - - - - d e */ + 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, /* l e t e - - - - */ + 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, /* t r a c e - - - */ + 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, /* - a c c e p t - */ + 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, /* - - - a c c e p */ + 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, /* t - c h a r s e */ + 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, /* t - - - - a c c */ + 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, /* e p t - e n c o */ + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, /* d i n g - - - - */ + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, /* a c c e p t - l */ + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, /* a n g u a g e - */ + 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, /* - - - a c c e p */ + 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, /* t - r a n g e s */ + 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, /* - - - - a g e - */ + 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, /* - - - a l l o w */ + 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, /* - - - - a u t h */ + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, /* o r i z a t i o */ + 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, /* n - - - - c a c */ + 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, /* h e - c o n t r */ + 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, /* o l - - - - c o */ + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, /* n n e c t i o n */ + 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */ + 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, /* e n t - b a s e */ + 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */ + 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, /* e n t - e n c o */ + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, /* d i n g - - - - */ + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, /* c o n t e n t - */ + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, /* l a n g u a g e */ + 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */ + 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, /* e n t - l e n g */ + 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, /* t h - - - - c o */ + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, /* n t e n t - l o */ + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, /* c a t i o n - - */ + 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, /* - - c o n t e n */ + 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, /* t - m d 5 - - - */ + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, /* - c o n t e n t */ + 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, /* - r a n g e - - */ + 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, /* - - c o n t e n */ + 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, /* t - t y p e - - */ + 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, /* - - d a t e - - */ + 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, /* - - e t a g - - */ + 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, /* - - e x p e c t */ + 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, /* - - - - e x p i */ + 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, /* r e s - - - - f */ + 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, /* r o m - - - - h */ + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, /* o s t - - - - i */ + 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, /* f - m a t c h - */ + 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, /* - - - i f - m o */ + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, /* d i f i e d - s */ + 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, /* i n c e - - - - */ + 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, /* i f - n o n e - */ + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, /* m a t c h - - - */ + 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, /* - i f - r a n g */ + 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, /* e - - - - i f - */ + 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, /* u n m o d i f i */ + 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, /* e d - s i n c e */ + 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, /* - - - - l a s t */ + 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, /* - m o d i f i e */ + 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, /* d - - - - l o c */ + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, /* a t i o n - - - */ + 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, /* - m a x - f o r */ + 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, /* w a r d s - - - */ + 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, /* - p r a g m a - */ + 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, /* - - - p r o x y */ + 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, /* - a u t h e n t */ + 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, /* i c a t e - - - */ + 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, /* - p r o x y - a */ + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, /* u t h o r i z a */ + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, /* t i o n - - - - */ + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, /* r a n g e - - - */ + 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, /* - r e f e r e r */ + 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, /* - - - - r e t r */ + 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, /* y - a f t e r - */ + 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, /* - - - s e r v e */ + 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, /* r - - - - t e - */ + 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, /* - - - t r a i l */ + 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, /* e r - - - - t r */ + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, /* a n s f e r - e */ + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, /* n c o d i n g - */ + 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, /* - - - u p g r a */ + 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, /* d e - - - - u s */ + 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, /* e r - a g e n t */ + 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, /* - - - - v a r y */ + 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, /* - - - - v i a - */ + 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, /* - - - w a r n i */ + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, /* n g - - - - w w */ + 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, /* w - a u t h e n */ + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, /* t i c a t e - - */ + 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, /* - - m e t h o d */ + 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, /* - - - - g e t - */ + 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, /* - - - s t a t u */ + 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, /* s - - - - 2 0 0 */ + 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, /* - O K - - - - v */ + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, /* e r s i o n - - */ + 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, /* - - H T T P - 1 */ + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, /* - 1 - - - - u r */ + 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, /* l - - - - p u b */ + 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, /* l i c - - - - s */ + 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, /* e t - c o o k i */ + 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, /* e - - - - k e e */ + 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, /* p - a l i v e - */ + 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, /* - - - o r i g i */ + 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, /* n 1 0 0 1 0 1 2 */ + 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, /* 0 1 2 0 2 2 0 5 */ + 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, /* 2 0 6 3 0 0 3 0 */ + 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, /* 2 3 0 3 3 0 4 3 */ + 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, /* 0 5 3 0 6 3 0 7 */ + 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, /* 4 0 2 4 0 5 4 0 */ + 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, /* 6 4 0 7 4 0 8 4 */ + 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, /* 0 9 4 1 0 4 1 1 */ + 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, /* 4 1 2 4 1 3 4 1 */ + 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, /* 4 4 1 5 4 1 6 4 */ + 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, /* 1 7 5 0 2 5 0 4 */ + 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, /* 5 0 5 2 0 3 - N */ + 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, /* o n - A u t h o */ + 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, /* r i t a t i v e */ + 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, /* - I n f o r m a */ + 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, /* t i o n 2 0 4 - */ + 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, /* N o - C o n t e */ + 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, /* n t 3 0 1 - M o */ + 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, /* v e d - P e r m */ + 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, /* a n e n t l y 4 */ + 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, /* 0 0 - B a d - R */ + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, /* e q u e s t 4 0 */ + 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, /* 1 - U n a u t h */ + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, /* o r i z e d 4 0 */ + 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, /* 3 - F o r b i d */ + 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, /* d e n 4 0 4 - N */ + 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, /* o t - F o u n d */ + 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, /* 5 0 0 - I n t e */ + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, /* r n a l - S e r */ + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, /* v e r - E r r o */ + 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, /* r 5 0 1 - N o t */ + 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, /* - I m p l e m e */ + 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, /* n t e d 5 0 3 - */ + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, /* S e r v i c e - */ + 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, /* U n a v a i l a */ + 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, /* b l e J a n - F */ + 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, /* e b - M a r - A */ + 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, /* p r - M a y - J */ + 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, /* u n - J u l - A */ + 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, /* u g - S e p t - */ + 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, /* O c t - N o v - */ + 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, /* D e c - 0 0 - 0 */ + 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, /* 0 - 0 0 - M o n */ + 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, /* - - T u e - - W */ + 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, /* e d - - T h u - */ + 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, /* - F r i - - S a */ + 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, /* t - - S u n - - */ + 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, /* G M T c h u n k */ + 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, /* e d - t e x t - */ + 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, /* h t m l - i m a */ + 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, /* g e - p n g - i */ + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, /* m a g e - j p g */ + 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, /* - i m a g e - g */ + 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, /* i f - a p p l i */ + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, /* c a t i o n - x */ + 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, /* m l - a p p l i */ + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, /* c a t i o n - x */ + 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, /* h t m l - x m l */ + 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, /* - t e x t - p l */ + 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, /* a i n - t e x t */ + 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, /* - j a v a s c r */ + 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, /* i p t - p u b l */ + 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, /* i c p r i v a t */ + 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, /* e m a x - a g e */ + 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, /* - g z i p - d e */ + 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, /* f l a t e - s d */ + 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, /* c h c h a r s e */ + 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, /* t - u t f - 8 c */ + 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, /* h a r s e t - i */ + 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, /* s o - 8 8 5 9 - */ + 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, /* 1 - u t f - - - */ + 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e /* - e n q - 0 - */ +}; + + +static ngx_http_spdy_request_header_t ngx_http_spdy_request_headers[] = { + { 0, 6, "method", ngx_http_spdy_parse_method }, + { 0, 6, "scheme", ngx_http_spdy_parse_scheme }, + { 0, 4, "host", ngx_http_spdy_parse_host }, + { 0, 4, "path", ngx_http_spdy_parse_path }, + { 0, 7, "version", ngx_http_spdy_parse_version }, +}; + +#define NGX_SPDY_REQUEST_HEADERS \ + (sizeof(ngx_http_spdy_request_headers) \ + / sizeof(ngx_http_spdy_request_header_t)) + + +void +ngx_http_spdy_init(ngx_event_t *rev) +{ + int rc; + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_main_conf_t *smcf; + ngx_http_spdy_connection_t *sc; + + c = rev->data; + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "init spdy request"); + + c->log->action = "processing SPDY"; + + smcf = ngx_http_get_module_main_conf(hc->conf_ctx, ngx_http_spdy_module); + + if (smcf->recv_buffer == NULL) { + smcf->recv_buffer = ngx_palloc(ngx_cycle->pool, smcf->recv_buffer_size); + if (smcf->recv_buffer == NULL) { + ngx_http_close_connection(c); + return; + } + } + + sc = ngx_pcalloc(c->pool, sizeof(ngx_http_spdy_connection_t)); + if (sc == NULL) { + ngx_http_close_connection(c); + return; + } + + sc->connection = c; + sc->http_connection = hc; + + sc->send_window = NGX_SPDY_CONNECTION_WINDOW; + sc->recv_window = NGX_SPDY_CONNECTION_WINDOW; + + sc->init_window = NGX_SPDY_INIT_STREAM_WINDOW; + + sc->handler = hc->proxy_protocol ? ngx_http_spdy_proxy_protocol + : ngx_http_spdy_state_head; + + sc->zstream_in.zalloc = ngx_http_spdy_zalloc; + sc->zstream_in.zfree = ngx_http_spdy_zfree; + sc->zstream_in.opaque = sc; + + rc = inflateInit(&sc->zstream_in); + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "inflateInit() failed: %d", rc); + ngx_http_close_connection(c); + return; + } + + sc->zstream_out.zalloc = ngx_http_spdy_zalloc; + sc->zstream_out.zfree = ngx_http_spdy_zfree; + sc->zstream_out.opaque = sc; + + sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_spdy_module); + + rc = deflateInit2(&sc->zstream_out, (int) sscf->headers_comp, + Z_DEFLATED, 11, 4, Z_DEFAULT_STRATEGY); + + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "deflateInit2() failed: %d", rc); + ngx_http_close_connection(c); + return; + } + + rc = deflateSetDictionary(&sc->zstream_out, ngx_http_spdy_dict, + sizeof(ngx_http_spdy_dict)); + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "deflateSetDictionary() failed: %d", rc); + ngx_http_close_connection(c); + return; + } + + sc->pool = ngx_create_pool(sscf->pool_size, sc->connection->log); + if (sc->pool == NULL) { + ngx_http_close_connection(c); + return; + } + + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_pool_cleanup_file_t)); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_spdy_pool_cleanup; + cln->data = sc; + + sc->streams_index = ngx_pcalloc(sc->pool, + ngx_http_spdy_streams_index_size(sscf) + * sizeof(ngx_http_spdy_stream_t *)); + if (sc->streams_index == NULL) { + ngx_http_close_connection(c); + return; + } + + if (ngx_http_spdy_send_settings(sc) == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (ngx_http_spdy_send_window_update(sc, 0, NGX_SPDY_MAX_WINDOW + - sc->recv_window) + == NGX_ERROR) + { + ngx_http_close_connection(c); + return; + } + + sc->recv_window = NGX_SPDY_MAX_WINDOW; + + ngx_queue_init(&sc->waiting); + ngx_queue_init(&sc->posted); + + c->data = sc; + + rev->handler = ngx_http_spdy_read_handler; + c->write->handler = ngx_http_spdy_write_handler; + + ngx_http_spdy_read_handler(rev); +} + + +static void +ngx_http_spdy_read_handler(ngx_event_t *rev) +{ + u_char *p, *end; + size_t available; + ssize_t n; + ngx_connection_t *c; + ngx_http_spdy_main_conf_t *smcf; + ngx_http_spdy_connection_t *sc; + + c = rev->data; + sc = c->data; + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy read handler"); + + sc->blocked = 1; + + smcf = ngx_http_get_module_main_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + available = smcf->recv_buffer_size - 2 * NGX_SPDY_STATE_BUFFER_SIZE; + + do { + p = smcf->recv_buffer; + + ngx_memcpy(p, sc->buffer, NGX_SPDY_STATE_BUFFER_SIZE); + end = p + sc->buffer_used; + + n = c->recv(c, end, available); + + if (n == NGX_AGAIN) { + break; + } + + if (n == 0 && (sc->incomplete || sc->processing)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client prematurely closed connection"); + } + + if (n == 0 || n == NGX_ERROR) { + ngx_http_spdy_finalize_connection(sc, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + end += n; + + sc->buffer_used = 0; + sc->incomplete = 0; + + do { + p = sc->handler(sc, p, end); + + if (p == NULL) { + return; + } + + } while (p != end); + + } while (rev->ready); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (sc->last_out && ngx_http_spdy_send_output_queue(sc) == NGX_ERROR) { + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + sc->blocked = 0; + + if (sc->processing) { + if (rev->timer_set) { + ngx_del_timer(rev); + } + return; + } + + ngx_http_spdy_handle_connection(sc); +} + + +static void +ngx_http_spdy_write_handler(ngx_event_t *wev) +{ + ngx_int_t rc; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_connection_t *sc; + + c = wev->data; + sc = c->data; + + if (wev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy write event timed out"); + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy write handler"); + + sc->blocked = 1; + + rc = ngx_http_spdy_send_output_queue(sc); + + if (rc == NGX_ERROR) { + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + while (!ngx_queue_empty(&sc->posted)) { + q = ngx_queue_head(&sc->posted); + + ngx_queue_remove(q); + + stream = ngx_queue_data(q, ngx_http_spdy_stream_t, queue); + + stream->handled = 0; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "run spdy stream %ui", stream->id); + + wev = stream->request->connection->write; + wev->handler(wev); + } + + sc->blocked = 0; + + if (rc == NGX_AGAIN) { + return; + } + + ngx_http_spdy_handle_connection(sc); +} + + +ngx_int_t +ngx_http_spdy_send_output_queue(ngx_http_spdy_connection_t *sc) +{ + int tcp_nodelay; + ngx_chain_t *cl; + ngx_event_t *wev; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + ngx_http_spdy_out_frame_t *out, *frame, *fn; + + c = sc->connection; + + if (c->error) { + return NGX_ERROR; + } + + wev = c->write; + + if (!wev->ready) { + return NGX_OK; + } + + cl = NULL; + out = NULL; + + for (frame = sc->last_out; frame; frame = fn) { + frame->last->next = cl; + cl = frame->first; + + fn = frame->next; + frame->next = out; + out = frame; + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy frame out: %p sid:%ui prio:%ui bl:%d len:%uz", + out, out->stream ? out->stream->id : 0, out->priority, + out->blocked, out->length); + } + + cl = c->send_chain(c, cl, 0); + + if (cl == NGX_CHAIN_ERROR) { + goto error; + } + + clcf = ngx_http_get_module_loc_conf(sc->http_connection->conf_ctx, + ngx_http_core_module); + + if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { + goto error; + } + + if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { + if (ngx_tcp_push(c->fd) == -1) { + ngx_connection_error(c, ngx_socket_errno, ngx_tcp_push_n " failed"); + goto error; + } + + c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; + tcp_nodelay = ngx_tcp_nodelay_and_tcp_nopush ? 1 : 0; + + } else { + tcp_nodelay = 1; + } + + if (tcp_nodelay + && clcf->tcp_nodelay + && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) + { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) + == -1) + { +#if (NGX_SOLARIS) + /* Solaris returns EINVAL if a socket has been shut down */ + c->log_error = NGX_ERROR_IGNORE_EINVAL; +#endif + + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + + c->log_error = NGX_ERROR_INFO; + goto error; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + if (cl) { + ngx_add_timer(wev, clcf->send_timeout); + + } else { + if (wev->timer_set) { + ngx_del_timer(wev); + } + } + + for ( /* void */ ; out; out = fn) { + fn = out->next; + + if (out->handler(sc, out) != NGX_OK) { + out->blocked = 1; + out->priority = NGX_SPDY_HIGHEST_PRIORITY; + break; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy frame sent: %p sid:%ui bl:%d len:%uz", + out, out->stream ? out->stream->id : 0, + out->blocked, out->length); + } + + frame = NULL; + + for ( /* void */ ; out; out = fn) { + fn = out->next; + out->next = frame; + frame = out; + } + + sc->last_out = frame; + + return NGX_OK; + +error: + + c->error = 1; + + if (!sc->blocked) { + ngx_post_event(wev, &ngx_posted_events); + } + + return NGX_ERROR; +} + + +static void +ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc) +{ + ngx_connection_t *c; + ngx_http_spdy_srv_conf_t *sscf; + + if (sc->last_out || sc->processing) { + return; + } + + c = sc->connection; + + if (c->error) { + ngx_http_close_connection(c); + return; + } + + if (c->buffered) { + return; + } + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + if (sc->incomplete) { + ngx_add_timer(c->read, sscf->recv_timeout); + return; + } + + if (ngx_terminate || ngx_exiting) { + ngx_http_close_connection(c); + return; + } + + ngx_destroy_pool(sc->pool); + + sc->pool = NULL; + sc->free_ctl_frames = NULL; + sc->free_fake_connections = NULL; + +#if (NGX_HTTP_SSL) + if (c->ssl) { + ngx_ssl_free_buffer(c); + } +#endif + + c->destroyed = 1; + c->idle = 1; + ngx_reusable_connection(c, 1); + + c->write->handler = ngx_http_empty_handler; + c->read->handler = ngx_http_spdy_keepalive_handler; + + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + ngx_add_timer(c->read, sscf->keepalive_timeout); +} + + +static u_char * +ngx_http_spdy_proxy_protocol(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_log_t *log; + + log = sc->connection->log; + log->action = "reading PROXY protocol"; + + pos = ngx_proxy_protocol_read(sc->connection, pos, end); + + log->action = "processing SPDY"; + + if (pos == NULL) { + return ngx_http_spdy_state_protocol_error(sc); + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_head(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + uint32_t head, flen; + ngx_uint_t type; + + if (end - pos < NGX_SPDY_FRAME_HEADER_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_head); + } + + head = ngx_spdy_frame_parse_uint32(pos); + + pos += sizeof(uint32_t); + + flen = ngx_spdy_frame_parse_uint32(pos); + + sc->flags = ngx_spdy_frame_flags(flen); + sc->length = ngx_spdy_frame_length(flen); + + pos += sizeof(uint32_t); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "process spdy frame head:%08XD f:%Xd l:%uz", + head, sc->flags, sc->length); + + if (ngx_spdy_ctl_frame_check(head)) { + type = ngx_spdy_ctl_frame_type(head); + + switch (type) { + + case NGX_SPDY_SYN_STREAM: + return ngx_http_spdy_state_syn_stream(sc, pos, end); + + case NGX_SPDY_SYN_REPLY: + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent unexpected SYN_REPLY frame"); + return ngx_http_spdy_state_protocol_error(sc); + + case NGX_SPDY_RST_STREAM: + return ngx_http_spdy_state_rst_stream(sc, pos, end); + + case NGX_SPDY_SETTINGS: + return ngx_http_spdy_state_settings(sc, pos, end); + + case NGX_SPDY_PING: + return ngx_http_spdy_state_ping(sc, pos, end); + + case NGX_SPDY_GOAWAY: + return ngx_http_spdy_state_skip(sc, pos, end); /* TODO */ + + case NGX_SPDY_HEADERS: + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent unexpected HEADERS frame"); + return ngx_http_spdy_state_protocol_error(sc); + + case NGX_SPDY_WINDOW_UPDATE: + return ngx_http_spdy_state_window_update(sc, pos, end); + + default: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy control frame with unknown type %ui", type); + return ngx_http_spdy_state_skip(sc, pos, end); + } + } + + if (ngx_spdy_data_frame_check(head)) { + sc->stream = ngx_http_spdy_get_stream_by_id(sc, head); + return ngx_http_spdy_state_data(sc, pos, end); + } + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent invalid frame"); + + return ngx_http_spdy_state_protocol_error(sc); +} + + +static u_char * +ngx_http_spdy_state_syn_stream(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_uint_t sid, prio; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_srv_conf_t *sscf; + + if (end - pos < NGX_SPDY_SYN_STREAM_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_syn_stream); + } + + if (sc->length <= NGX_SPDY_SYN_STREAM_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame with incorrect length %uz", + sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sc->length -= NGX_SPDY_SYN_STREAM_SIZE; + + sid = ngx_spdy_frame_parse_sid(pos); + prio = pos[8] >> 5; + + pos += NGX_SPDY_SYN_STREAM_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy SYN_STREAM frame sid:%ui prio:%ui", sid, prio); + + if (sid % 2 == 0 || sid <= sc->last_sid) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame " + "with invalid Stream-ID %ui", sid); + + stream = ngx_http_spdy_get_stream_by_id(sc, sid); + + if (stream) { + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_PROTOCOL_ERROR) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + } + + return ngx_http_spdy_state_protocol_error(sc); + } + + sc->last_sid = sid; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + if (sc->processing >= sscf->concurrent_streams) { + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "concurrent streams exceeded %ui", sc->processing); + + if (ngx_http_spdy_send_rst_stream(sc, sid, NGX_SPDY_REFUSED_STREAM, + prio) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + stream = ngx_http_spdy_create_stream(sc, sid, prio); + if (stream == NULL) { + return ngx_http_spdy_state_internal_error(sc); + } + + stream->in_closed = (sc->flags & NGX_SPDY_FLAG_FIN) ? 1 : 0; + + stream->request->request_length = NGX_SPDY_FRAME_HEADER_SIZE + + NGX_SPDY_SYN_STREAM_SIZE + + sc->length; + + sc->stream = stream; + + return ngx_http_spdy_state_headers(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_headers(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + int z; + size_t size; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_http_request_t *r; + + size = end - pos; + + if (size == 0) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + } + + if (size > sc->length) { + size = sc->length; + } + + r = sc->stream->request; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "process spdy header block %uz of %uz", size, sc->length); + + buf = r->header_in; + + sc->zstream_in.next_in = pos; + sc->zstream_in.avail_in = size; + sc->zstream_in.next_out = buf->last; + + /* one byte is reserved for null-termination of the last header value */ + sc->zstream_in.avail_out = buf->end - buf->last - 1; + + z = inflate(&sc->zstream_in, Z_NO_FLUSH); + + if (z == Z_NEED_DICT) { + z = inflateSetDictionary(&sc->zstream_in, ngx_http_spdy_dict, + sizeof(ngx_http_spdy_dict)); + + if (z != Z_OK) { + if (z == Z_DATA_ERROR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent SYN_STREAM frame with header " + "block encoded using wrong dictionary: %ul", + (u_long) sc->zstream_in.adler); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "inflateSetDictionary() failed: %d", z); + + return ngx_http_spdy_state_internal_error(sc); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy inflateSetDictionary(): %d", z); + + z = sc->zstream_in.avail_in ? inflate(&sc->zstream_in, Z_NO_FLUSH) + : Z_OK; + } + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_in.next_in, sc->zstream_in.next_out, + sc->zstream_in.avail_in, sc->zstream_in.avail_out, + z); + + if (z != Z_OK) { + return ngx_http_spdy_state_inflate_error(sc, z); + } + + sc->length -= sc->zstream_in.next_in - pos; + pos = sc->zstream_in.next_in; + + buf->last = sc->zstream_in.next_out; + + if (r->headers_in.headers.part.elts == NULL) { + + if (buf->last - buf->pos < NGX_SPDY_NV_NUM_SIZE) { + + if (sc->length == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "premature end of spdy header block"); + + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + } + + sc->entries = ngx_spdy_frame_parse_uint32(buf->pos); + + buf->pos += NGX_SPDY_NV_NUM_SIZE; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy header block has %ui entries", + sc->entries); + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + if (ngx_array_init(&r->headers_in.cookies, r->pool, 2, + sizeof(ngx_table_elt_t *)) + != NGX_OK) + { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + } + + while (sc->entries) { + + rc = ngx_http_spdy_parse_header(r); + + switch (rc) { + + case NGX_DONE: + sc->entries--; + + case NGX_OK: + break; + + case NGX_AGAIN: + + if (sc->zstream_in.avail_in) { + + rc = ngx_http_spdy_alloc_large_header_buffer(r); + + if (rc == NGX_DECLINED) { + ngx_http_finalize_request(r, + NGX_HTTP_REQUEST_HEADER_TOO_LARGE); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + if (rc != NGX_OK) { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + /* null-terminate the last processed header name or value */ + *buf->pos = '\0'; + + buf = r->header_in; + + sc->zstream_in.next_out = buf->last; + + /* one byte is reserved for null-termination */ + sc->zstream_in.avail_out = buf->end - buf->last - 1; + + z = inflate(&sc->zstream_in, Z_NO_FLUSH); + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_in.next_in, sc->zstream_in.next_out, + sc->zstream_in.avail_in, sc->zstream_in.avail_out, + z); + + if (z != Z_OK) { + return ngx_http_spdy_state_inflate_error(sc, z); + } + + sc->length -= sc->zstream_in.next_in - pos; + pos = sc->zstream_in.next_in; + + buf->last = sc->zstream_in.next_out; + + continue; + } + + if (sc->length == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "premature end of spdy header block"); + + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + + case NGX_HTTP_PARSE_INVALID_HEADER: + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + + default: /* NGX_ERROR */ + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + /* a header line has been parsed successfully */ + + rc = ngx_http_spdy_handle_request_header(r); + + if (rc != NGX_OK) { + if (rc == NGX_HTTP_PARSE_INVALID_HEADER) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + if (rc != NGX_ABORT) { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + } + + if (buf->pos != buf->last || sc->zstream_in.avail_in) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "incorrect number of spdy header block entries"); + + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + if (sc->length) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + } + + /* null-terminate the last header value */ + *buf->pos = '\0'; + + ngx_http_spdy_run_request(r); + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_headers_skip(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + int n; + size_t size; + u_char buffer[NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE]; + + if (sc->length == 0) { + return ngx_http_spdy_state_complete(sc, pos, end); + } + + size = end - pos; + + if (size == 0) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers_skip); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy header block skip %uz of %uz", size, sc->length); + + sc->zstream_in.next_in = pos; + sc->zstream_in.avail_in = (size < sc->length) ? size : sc->length; + + while (sc->zstream_in.avail_in) { + sc->zstream_in.next_out = buffer; + sc->zstream_in.avail_out = NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE; + + n = inflate(&sc->zstream_in, Z_NO_FLUSH); + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_in.next_in, sc->zstream_in.next_out, + sc->zstream_in.avail_in, sc->zstream_in.avail_out, + n); + + if (n != Z_OK) { + return ngx_http_spdy_state_inflate_error(sc, n); + } + } + + pos = sc->zstream_in.next_in; + + if (size < sc->length) { + sc->length -= size; + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers_skip); + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_headers_error(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_http_spdy_stream_t *stream; + + stream = sc->stream; + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame for stream %ui " + "with invalid header block", stream->id); + + if (ngx_http_spdy_send_rst_stream(sc, stream->id, NGX_SPDY_PROTOCOL_ERROR, + stream->priority) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + + stream->out_closed = 1; + + ngx_http_spdy_close_stream(stream, NGX_HTTP_BAD_REQUEST); + + return ngx_http_spdy_state_headers_skip(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_window_update(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + size_t delta; + ngx_uint_t sid; + ngx_event_t *wev; + ngx_queue_t *q; + ngx_http_spdy_stream_t *stream; + + if (end - pos < NGX_SPDY_WINDOW_UPDATE_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_window_update); + } + + if (sc->length != NGX_SPDY_WINDOW_UPDATE_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent WINDOW_UPDATE frame " + "with incorrect length %uz", sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sid = ngx_spdy_frame_parse_sid(pos); + + pos += NGX_SPDY_SID_SIZE; + + delta = ngx_spdy_frame_parse_delta(pos); + + pos += NGX_SPDY_DELTA_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy WINDOW_UPDATE sid:%ui delta:%uz", sid, delta); + + if (sid) { + stream = ngx_http_spdy_get_stream_by_id(sc, sid); + + if (stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "unknown spdy stream"); + + return ngx_http_spdy_state_complete(sc, pos, end); + } + + if (stream->send_window > (ssize_t) (NGX_SPDY_MAX_WINDOW - delta)) { + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated flow control for stream %ui: " + "received WINDOW_UPDATE frame with delta %uz " + "not allowed for window %z", + sid, delta, stream->send_window); + + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_FLOW_CONTROL_ERROR) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_complete(sc, pos, end); + } + + stream->send_window += delta; + + if (stream->exhausted) { + stream->exhausted = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + } + } + + } else { + sc->send_window += delta; + + if (sc->send_window > NGX_SPDY_MAX_WINDOW) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated connection flow control: " + "received WINDOW_UPDATE frame with delta %uz " + "not allowed for window %uz", + delta, sc->send_window); + + return ngx_http_spdy_state_protocol_error(sc); + } + + while (!ngx_queue_empty(&sc->waiting)) { + q = ngx_queue_head(&sc->waiting); + + ngx_queue_remove(q); + + stream = ngx_queue_data(q, ngx_http_spdy_stream_t, queue); + + stream->handled = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + + if (sc->send_window == 0) { + break; + } + } + } + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_data(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_http_spdy_stream_t *stream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy DATA frame"); + + if (sc->length > sc->recv_window) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated connection flow control: " + "received DATA frame length %uz, available window %uz", + sc->length, sc->recv_window); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sc->recv_window -= sc->length; + + if (sc->recv_window < NGX_SPDY_MAX_WINDOW / 4) { + + if (ngx_http_spdy_send_window_update(sc, 0, + NGX_SPDY_MAX_WINDOW + - sc->recv_window) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + sc->recv_window = NGX_SPDY_MAX_WINDOW; + } + + stream = sc->stream; + + if (stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "unknown spdy stream"); + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + if (sc->length > stream->recv_window) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated flow control for stream %ui: " + "received DATA frame length %uz, available window %uz", + stream->id, sc->length, stream->recv_window); + + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_FLOW_CONTROL_ERROR) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + stream->recv_window -= sc->length; + + if (stream->recv_window < NGX_SPDY_STREAM_WINDOW / 4) { + + if (ngx_http_spdy_send_window_update(sc, stream->id, + NGX_SPDY_STREAM_WINDOW + - stream->recv_window) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + stream->recv_window = NGX_SPDY_STREAM_WINDOW; + } + + if (stream->in_closed) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent DATA frame for half-closed stream %ui", + stream->id); + + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_STREAM_ALREADY_CLOSED) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + return ngx_http_spdy_state_read_data(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_read_data(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + size_t size; + ssize_t n; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_temp_file_t *tf; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + stream = sc->stream; + + if (stream == NULL) { + return ngx_http_spdy_state_skip(sc, pos, end); + } + + if (stream->skip_data) { + + if (sc->flags & NGX_SPDY_FLAG_FIN) { + stream->in_closed = 1; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "skipping spdy DATA frame, reason: %d", + stream->skip_data); + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + size = end - pos; + + if (size > sc->length) { + size = sc->length; + } + + r = stream->request; + + if (r->request_body == NULL + && ngx_http_spdy_init_request_body(r) != NGX_OK) + { + stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR; + return ngx_http_spdy_state_skip(sc, pos, end); + } + + rb = r->request_body; + tf = rb->temp_file; + buf = rb->buf; + + if (size) { + rb->rest += size; + + if (r->headers_in.content_length_n != -1 + && r->headers_in.content_length_n < rb->rest) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client intended to send body data " + "larger than declared"); + + stream->skip_data = NGX_SPDY_DATA_ERROR; + goto error; + + } else { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->client_max_body_size + && clcf->client_max_body_size < rb->rest) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send " + "too large chunked body: %O bytes", rb->rest); + + stream->skip_data = NGX_SPDY_DATA_ERROR; + goto error; + } + } + + sc->length -= size; + + if (tf) { + buf->start = pos; + buf->pos = pos; + + pos += size; + + buf->end = pos; + buf->last = pos; + + n = ngx_write_chain_to_temp_file(tf, rb->bufs); + + /* TODO: n == 0 or not complete and level event */ + + if (n == NGX_ERROR) { + stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR; + goto error; + } + + tf->offset += n; + + } else { + buf->last = ngx_cpymem(buf->last, pos, size); + pos += size; + } + + r->request_length += size; + } + + if (sc->length) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_read_data); + } + + if (sc->flags & NGX_SPDY_FLAG_FIN) { + + stream->in_closed = 1; + + if (r->headers_in.content_length_n < 0) { + r->headers_in.content_length_n = rb->rest; + + } else if (r->headers_in.content_length_n != rb->rest) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream: " + "only %O out of %O bytes of request body received", + rb->rest, r->headers_in.content_length_n); + + stream->skip_data = NGX_SPDY_DATA_ERROR; + goto error; + } + + if (tf) { + ngx_memzero(buf, sizeof(ngx_buf_t)); + + buf->in_file = 1; + buf->file_last = tf->file.offset; + buf->file = &tf->file; + + rb->buf = NULL; + } + + if (rb->post_handler) { + r->read_event_handler = ngx_http_block_reading; + rb->post_handler(r); + } + } + + return ngx_http_spdy_state_complete(sc, pos, end); + +error: + + if (rb->post_handler) { + + if (stream->skip_data == NGX_SPDY_DATA_ERROR) { + rc = (r->headers_in.content_length_n == -1) + ? NGX_HTTP_REQUEST_ENTITY_TOO_LARGE + : NGX_HTTP_BAD_REQUEST; + + } else { + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_http_finalize_request(r, rc); + } + + return ngx_http_spdy_state_skip(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_rst_stream(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_uint_t sid, status; + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_spdy_stream_t *stream; + + if (end - pos < NGX_SPDY_RST_STREAM_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_rst_stream); + } + + if (sc->length != NGX_SPDY_RST_STREAM_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent RST_STREAM frame with incorrect length %uz", + sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sid = ngx_spdy_frame_parse_sid(pos); + + pos += NGX_SPDY_SID_SIZE; + + status = ngx_spdy_frame_parse_uint32(pos); + + pos += sizeof(uint32_t); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy RST_STREAM sid:%ui st:%ui", sid, status); + + stream = ngx_http_spdy_get_stream_by_id(sc, sid); + + if (stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "unknown spdy stream"); + + return ngx_http_spdy_state_complete(sc, pos, end); + } + + stream->in_closed = 1; + stream->out_closed = 1; + + fc = stream->request->connection; + fc->error = 1; + + switch (status) { + + case NGX_SPDY_CANCEL: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client canceled stream %ui", sid); + break; + + case NGX_SPDY_INTERNAL_ERROR: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client terminated stream %ui due to internal error", + sid); + break; + + default: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client terminated stream %ui with status %ui", + sid, status); + break; + } + + ev = fc->read; + ev->handler(ev); + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_ping(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + if (end - pos < NGX_SPDY_PING_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_ping); + } + + if (sc->length != NGX_SPDY_PING_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent PING frame with incorrect length %uz", + sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy PING frame"); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_PING_SIZE, + NGX_SPDY_HIGHEST_PRIORITY); + if (frame == NULL) { + return ngx_http_spdy_state_internal_error(sc); + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_PING); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_PING_SIZE); + + p = ngx_cpymem(p, pos, NGX_SPDY_PING_SIZE); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + pos += NGX_SPDY_PING_SIZE; + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_skip(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + size_t size; + + size = end - pos; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy frame skip %uz of %uz", size, sc->length); + + if (size < sc->length) { + sc->length -= size; + return ngx_http_spdy_state_save(sc, end, end, + ngx_http_spdy_state_skip); + } + + return ngx_http_spdy_state_complete(sc, pos + sc->length, end); +} + + +static u_char * +ngx_http_spdy_state_settings(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_uint_t fid, val; + + if (sc->entries == 0) { + + if (end - pos < NGX_SPDY_SETTINGS_NUM_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_settings); + } + + sc->entries = ngx_spdy_frame_parse_uint32(pos); + + pos += NGX_SPDY_SETTINGS_NUM_SIZE; + sc->length -= NGX_SPDY_SETTINGS_NUM_SIZE; + + if (sc->length < sc->entries * NGX_SPDY_SETTINGS_PAIR_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SETTINGS frame with incorrect " + "length %uz or number of entries %ui", + sc->length, sc->entries); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy SETTINGS frame has %ui entries", sc->entries); + } + + while (sc->entries) { + if (end - pos < NGX_SPDY_SETTINGS_PAIR_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_settings); + } + + sc->entries--; + sc->length -= NGX_SPDY_SETTINGS_PAIR_SIZE; + + fid = ngx_spdy_frame_parse_uint32(pos); + + pos += NGX_SPDY_SETTINGS_FID_SIZE; + + val = ngx_spdy_frame_parse_uint32(pos); + + pos += NGX_SPDY_SETTINGS_VAL_SIZE; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy SETTINGS entry fl:%ui id:%ui val:%ui", + ngx_spdy_frame_flags(fid), ngx_spdy_frame_id(fid), val); + + if (ngx_spdy_frame_flags(fid) == NGX_SPDY_SETTINGS_FLAG_PERSISTED) { + continue; + } + + switch (ngx_spdy_frame_id(fid)) { + + case NGX_SPDY_SETTINGS_INIT_WINDOW: + + if (val > NGX_SPDY_MAX_WINDOW) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SETTINGS frame with " + "incorrect INIT_WINDOW value: %ui", val); + + return ngx_http_spdy_state_protocol_error(sc); + } + + if (ngx_http_spdy_adjust_windows(sc, val - sc->init_window) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + + sc->init_window = val; + + continue; + } + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_complete(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy frame complete pos:%p end:%p", pos, end); + + if (pos > end) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "receive buffer overrun"); + + ngx_debug_point(); + return ngx_http_spdy_state_internal_error(sc); + } + + sc->handler = ngx_http_spdy_state_head; + sc->stream = NULL; + + return pos; +} + + +static u_char * +ngx_http_spdy_state_save(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end, ngx_http_spdy_handler_pt handler) +{ + size_t size; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy frame state save pos:%p end:%p handler:%p", + pos, end, handler); + + size = end - pos; + + if (size > NGX_SPDY_STATE_BUFFER_SIZE) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "state buffer overflow: %uz bytes required", size); + + ngx_debug_point(); + return ngx_http_spdy_state_internal_error(sc); + } + + ngx_memcpy(sc->buffer, pos, NGX_SPDY_STATE_BUFFER_SIZE); + + sc->buffer_used = size; + sc->handler = handler; + sc->incomplete = 1; + + return end; +} + + +static u_char * +ngx_http_spdy_state_inflate_error(ngx_http_spdy_connection_t *sc, int rc) +{ + if (rc == Z_DATA_ERROR || rc == Z_STREAM_END) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame with " + "corrupted header block, inflate() failed: %d", rc); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_error(NGX_LOG_ERR, sc->connection->log, 0, + "inflate() failed: %d", rc); + + return ngx_http_spdy_state_internal_error(sc); +} + + +static u_char * +ngx_http_spdy_state_protocol_error(ngx_http_spdy_connection_t *sc) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy state protocol error"); + + if (sc->stream) { + sc->stream->out_closed = 1; + ngx_http_spdy_close_stream(sc->stream, NGX_HTTP_BAD_REQUEST); + } + + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + + return NULL; +} + + +static u_char * +ngx_http_spdy_state_internal_error(ngx_http_spdy_connection_t *sc) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy state internal error"); + + if (sc->stream) { + sc->stream->out_closed = 1; + ngx_http_spdy_close_stream(sc->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_INTERNAL_SERVER_ERROR); + + return NULL; +} + + +static ngx_int_t +ngx_http_spdy_send_window_update(ngx_http_spdy_connection_t *sc, ngx_uint_t sid, + ngx_uint_t delta) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send WINDOW_UPDATE sid:%ui delta:%ui", sid, delta); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_WINDOW_UPDATE_SIZE, + NGX_SPDY_HIGHEST_PRIORITY); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_WINDOW_UPDATE); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_WINDOW_UPDATE_SIZE); + + p = ngx_spdy_frame_write_sid(p, sid); + p = ngx_spdy_frame_aligned_write_uint32(p, delta); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_send_rst_stream(ngx_http_spdy_connection_t *sc, ngx_uint_t sid, + ngx_uint_t status, ngx_uint_t priority) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + if (sc->connection->error) { + return NGX_OK; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send RST_STREAM sid:%ui st:%ui", sid, status); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_RST_STREAM_SIZE, + priority); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_RST_STREAM); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_RST_STREAM_SIZE); + + p = ngx_spdy_frame_write_sid(p, sid); + p = ngx_spdy_frame_aligned_write_uint32(p, status); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} + + +#if 0 +static ngx_int_t +ngx_http_spdy_send_goaway(ngx_http_spdy_connection_t *sc) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send GOAWAY sid:%ui", sc->last_sid); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_GOAWAY_SIZE, + NGX_SPDY_HIGHEST_PRIORITY); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_GOAWAY); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_GOAWAY_SIZE); + + p = ngx_spdy_frame_write_sid(p, sc->last_sid); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} +#endif + + +static ngx_int_t +ngx_http_spdy_send_settings(ngx_http_spdy_connection_t *sc) +{ + u_char *p; + ngx_buf_t *buf; + ngx_chain_t *cl; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_out_frame_t *frame; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send SETTINGS frame"); + + frame = ngx_palloc(sc->pool, sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NGX_ERROR; + } + + cl = ngx_alloc_chain_link(sc->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + buf = ngx_create_temp_buf(sc->pool, NGX_SPDY_FRAME_HEADER_SIZE + + NGX_SPDY_SETTINGS_NUM_SIZE + + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE); + if (buf == NULL) { + return NGX_ERROR; + } + + buf->last_buf = 1; + + cl->buf = buf; + cl->next = NULL; + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_spdy_settings_frame_handler; + frame->stream = NULL; +#if (NGX_DEBUG) + frame->length = NGX_SPDY_SETTINGS_NUM_SIZE + + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE; +#endif + frame->priority = NGX_SPDY_HIGHEST_PRIORITY; + frame->blocked = 0; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_SETTINGS); + p = ngx_spdy_frame_write_flags_and_len(p, NGX_SPDY_FLAG_CLEAR_SETTINGS, + NGX_SPDY_SETTINGS_NUM_SIZE + + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE); + + p = ngx_spdy_frame_aligned_write_uint32(p, 2); + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + p = ngx_spdy_frame_write_flags_and_id(p, 0, NGX_SPDY_SETTINGS_MAX_STREAMS); + p = ngx_spdy_frame_aligned_write_uint32(p, sscf->concurrent_streams); + + p = ngx_spdy_frame_write_flags_and_id(p, 0, NGX_SPDY_SETTINGS_INIT_WINDOW); + p = ngx_spdy_frame_aligned_write_uint32(p, NGX_SPDY_STREAM_WINDOW); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_http_spdy_settings_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + ngx_free_chain(sc->pool, frame->first); + + return NGX_OK; +} + + +static ngx_http_spdy_out_frame_t * +ngx_http_spdy_get_ctl_frame(ngx_http_spdy_connection_t *sc, size_t length, + ngx_uint_t priority) +{ + ngx_chain_t *cl; + ngx_http_spdy_out_frame_t *frame; + + frame = sc->free_ctl_frames; + + if (frame) { + sc->free_ctl_frames = frame->next; + + cl = frame->first; + cl->buf->pos = cl->buf->start; + + } else { + frame = ngx_palloc(sc->pool, sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NULL; + } + + cl = ngx_alloc_chain_link(sc->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = ngx_create_temp_buf(sc->pool, + NGX_SPDY_CTL_FRAME_BUFFER_SIZE); + if (cl->buf == NULL) { + return NULL; + } + + cl->buf->last_buf = 1; + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_spdy_ctl_frame_handler; + frame->stream = NULL; + } + +#if (NGX_DEBUG) + if (length > NGX_SPDY_CTL_FRAME_BUFFER_SIZE - NGX_SPDY_FRAME_HEADER_SIZE) { + ngx_log_error(NGX_LOG_ALERT, sc->pool->log, 0, + "requested control frame is too large: %uz", length); + return NULL; + } + + frame->length = length; +#endif + + frame->priority = priority; + frame->blocked = 0; + + return frame; +} + + +static ngx_int_t +ngx_http_spdy_ctl_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + frame->next = sc->free_ctl_frames; + sc->free_ctl_frames = frame; + + return NGX_OK; +} + + +static ngx_http_spdy_stream_t * +ngx_http_spdy_create_stream(ngx_http_spdy_connection_t *sc, ngx_uint_t id, + ngx_uint_t priority) +{ + ngx_log_t *log; + ngx_uint_t index; + ngx_event_t *rev, *wev; + ngx_connection_t *fc; + ngx_http_log_ctx_t *ctx; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_core_srv_conf_t *cscf; + ngx_http_spdy_srv_conf_t *sscf; + + fc = sc->free_fake_connections; + + if (fc) { + sc->free_fake_connections = fc->data; + + rev = fc->read; + wev = fc->write; + log = fc->log; + ctx = log->data; + + } else { + fc = ngx_palloc(sc->pool, sizeof(ngx_connection_t)); + if (fc == NULL) { + return NULL; + } + + rev = ngx_palloc(sc->pool, sizeof(ngx_event_t)); + if (rev == NULL) { + return NULL; + } + + wev = ngx_palloc(sc->pool, sizeof(ngx_event_t)); + if (wev == NULL) { + return NULL; + } + + log = ngx_palloc(sc->pool, sizeof(ngx_log_t)); + if (log == NULL) { + return NULL; + } + + ctx = ngx_palloc(sc->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + return NULL; + } + + ctx->connection = fc; + ctx->request = NULL; + } + + ngx_memcpy(log, sc->connection->log, sizeof(ngx_log_t)); + + log->data = ctx; + + ngx_memzero(rev, sizeof(ngx_event_t)); + + rev->data = fc; + rev->ready = 1; + rev->handler = ngx_http_spdy_close_stream_handler; + rev->log = log; + + ngx_memcpy(wev, rev, sizeof(ngx_event_t)); + + wev->write = 1; + + ngx_memcpy(fc, sc->connection, sizeof(ngx_connection_t)); + + fc->data = sc->http_connection; + fc->read = rev; + fc->write = wev; + fc->sent = 0; + fc->log = log; + fc->buffered = 0; + fc->sndlowat = 1; + fc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; + + r = ngx_http_create_request(fc); + if (r == NULL) { + return NULL; + } + + r->valid_location = 1; + + fc->data = r; + sc->connection->requests++; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_in = ngx_create_temp_buf(r->pool, + cscf->client_header_buffer_size); + if (r->header_in == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; + + stream = ngx_pcalloc(r->pool, sizeof(ngx_http_spdy_stream_t)); + if (stream == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->spdy_stream = stream; + + stream->id = id; + stream->request = r; + stream->connection = sc; + + stream->send_window = sc->init_window; + stream->recv_window = NGX_SPDY_STREAM_WINDOW; + + stream->priority = priority; + + sscf = ngx_http_get_module_srv_conf(r, ngx_http_spdy_module); + + index = ngx_http_spdy_stream_index(sscf, id); + + stream->index = sc->streams_index[index]; + sc->streams_index[index] = stream; + + sc->processing++; + + return stream; +} + + +static ngx_http_spdy_stream_t * +ngx_http_spdy_get_stream_by_id(ngx_http_spdy_connection_t *sc, + ngx_uint_t sid) +{ + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_srv_conf_t *sscf; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + stream = sc->streams_index[ngx_http_spdy_stream_index(sscf, sid)]; + + while (stream) { + if (stream->id == sid) { + return stream; + } + + stream = stream->index; + } + + return NULL; +} + + +static ngx_int_t +ngx_http_spdy_parse_header(ngx_http_request_t *r) +{ + u_char *p, *end, ch; + ngx_uint_t hash; + ngx_http_core_srv_conf_t *cscf; + + enum { + sw_name_len = 0, + sw_name, + sw_value_len, + sw_value + } state; + + state = r->state; + + p = r->header_in->pos; + end = r->header_in->last; + + switch (state) { + + case sw_name_len: + + if (end - p < NGX_SPDY_NV_NLEN_SIZE) { + return NGX_AGAIN; + } + + r->lowcase_index = ngx_spdy_frame_parse_uint32(p); + + if (r->lowcase_index == 0) { + return NGX_ERROR; + } + + /* null-terminate the previous header value */ + *p = '\0'; + + p += NGX_SPDY_NV_NLEN_SIZE; + + r->invalid_header = 0; + + state = sw_name; + + /* fall through */ + + case sw_name: + + if ((ngx_uint_t) (end - p) < r->lowcase_index) { + break; + } + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_name_start = p; + r->header_name_end = p + r->lowcase_index; + + if (p[0] == ':') { + p++; + } + + hash = 0; + + for ( /* void */ ; p != r->header_name_end; p++) { + + ch = *p; + + hash = ngx_hash(hash, ch); + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + switch (ch) { + case '\0': + case LF: + case CR: + case ':': + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%*s\"", + r->lowcase_index, r->header_name_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (ch >= 'A' && ch <= 'Z') { + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + r->header_hash = hash; + + state = sw_value_len; + + /* fall through */ + + case sw_value_len: + + if (end - p < NGX_SPDY_NV_VLEN_SIZE) { + break; + } + + r->lowcase_index = ngx_spdy_frame_parse_uint32(p); + + /* null-terminate header name */ + *p = '\0'; + + p += NGX_SPDY_NV_VLEN_SIZE; + + state = sw_value; + + /* fall through */ + + case sw_value: + + if ((ngx_uint_t) (end - p) < r->lowcase_index) { + break; + } + + r->header_start = p; + + while (r->lowcase_index--) { + ch = *p; + + if (ch == '\0') { + + if (p == r->header_start) { + return NGX_ERROR; + } + + r->header_end = p; + r->header_in->pos = p + 1; + + r->state = sw_value; + + return NGX_OK; + } + + if (ch == CR || ch == LF) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%*s\" with " + "invalid value: \"%*s\\%c...\"", + r->header_name_end - r->header_name_start, + r->header_name_start, + p - r->header_start, + r->header_start, + ch == CR ? 'r' : 'n'); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + p++; + } + + r->header_end = p; + r->header_in->pos = p; + + r->state = 0; + + return NGX_DONE; + } + + r->header_in->pos = p; + r->state = state; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_spdy_alloc_large_header_buffer(ngx_http_request_t *r) +{ + u_char *old, *new, *p; + size_t rest; + ngx_buf_t *buf; + ngx_http_spdy_stream_t *stream; + ngx_http_core_srv_conf_t *cscf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy alloc large header buffer"); + + stream = r->spdy_stream; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (stream->header_buffers + == (ngx_uint_t) cscf->large_client_header_buffers.num) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too large request"); + + return NGX_DECLINED; + } + + rest = r->header_in->last - r->header_in->pos; + + /* + * One more byte is needed for null-termination + * and another one for further progress. + */ + if (rest > cscf->large_client_header_buffers.size - 2) { + p = r->header_in->pos; + + if (rest > NGX_MAX_ERROR_STR - 300) { + rest = NGX_MAX_ERROR_STR - 300; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too long header name or value: \"%*s...\"", + rest, p); + + return NGX_DECLINED; + } + + buf = ngx_create_temp_buf(r->pool, cscf->large_client_header_buffers.size); + if (buf == NULL) { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy large header alloc: %p %uz", + buf->pos, buf->end - buf->last); + + old = r->header_in->pos; + new = buf->pos; + + if (rest) { + buf->last = ngx_cpymem(new, old, rest); + } + + r->header_in = buf; + + stream->header_buffers++; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_handle_request_header(ngx_http_request_t *r) +{ + ngx_uint_t i; + ngx_table_elt_t *h; + ngx_http_core_srv_conf_t *cscf; + ngx_http_spdy_request_header_t *sh; + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%*s\"", + r->header_end - r->header_name_start, + r->header_name_start); + return NGX_OK; + } + + } + + if (r->header_name_start[0] == ':') { + r->header_name_start++; + + for (i = 0; i < NGX_SPDY_REQUEST_HEADERS; i++) { + sh = &ngx_http_spdy_request_headers[i]; + + if (sh->hash != r->header_hash + || sh->len != r->header_name_end - r->header_name_start + || ngx_strncmp(sh->header, r->header_name_start, sh->len) != 0) + { + continue; + } + + return sh->handler(r); + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \":%*s\"", + r->header_end - r->header_name_start, + r->header_name_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = r->header_hash; + + h->key.len = r->header_name_end - r->header_name_start; + h->key.data = r->header_name_start; + + h->value.len = r->header_end - r->header_start; + h->value.data = r->header_start; + + h->lowcase_key = h->key.data; + + return NGX_OK; +} + + +void +ngx_http_spdy_request_headers_init(void) +{ + ngx_uint_t i; + ngx_http_spdy_request_header_t *h; + + for (i = 0; i < NGX_SPDY_REQUEST_HEADERS; i++) { + h = &ngx_http_spdy_request_headers[i]; + h->hash = ngx_hash_key(h->header, h->len); + } +} + + +static ngx_int_t +ngx_http_spdy_parse_method(ngx_http_request_t *r) +{ + size_t k, len; + ngx_uint_t n; + const u_char *p, *m; + + /* + * This array takes less than 256 sequential bytes, + * and if typical CPU cache line size is 64 bytes, + * it is prefetched for 4 load operations. + */ + static const struct { + u_char len; + const u_char method[11]; + uint32_t value; + } tests[] = { + { 3, "GET", NGX_HTTP_GET }, + { 4, "POST", NGX_HTTP_POST }, + { 4, "HEAD", NGX_HTTP_HEAD }, + { 7, "OPTIONS", NGX_HTTP_OPTIONS }, + { 8, "PROPFIND", NGX_HTTP_PROPFIND }, + { 3, "PUT", NGX_HTTP_PUT }, + { 5, "MKCOL", NGX_HTTP_MKCOL }, + { 6, "DELETE", NGX_HTTP_DELETE }, + { 4, "COPY", NGX_HTTP_COPY }, + { 4, "MOVE", NGX_HTTP_MOVE }, + { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, + { 4, "LOCK", NGX_HTTP_LOCK }, + { 6, "UNLOCK", NGX_HTTP_UNLOCK }, + { 5, "PATCH", NGX_HTTP_PATCH }, + { 5, "TRACE", NGX_HTTP_TRACE } + }, *test; + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :method header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + len = r->header_end - r->header_start; + + r->method_name.len = len; + r->method_name.data = r->header_start; + + test = tests; + n = sizeof(tests) / sizeof(tests[0]); + + do { + if (len == test->len) { + p = r->method_name.data; + m = test->method; + k = len; + + do { + if (*p++ != *m++) { + goto next; + } + } while (--k); + + r->method = test->value; + return NGX_OK; + } + + next: + test++; + + } while (--n); + + p = r->method_name.data; + + do { + if ((*p < 'A' || *p > 'Z') && *p != '_') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", + &r->method_name); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + p++; + + } while (--len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_scheme(ngx_http_request_t *r) +{ + if (r->schema_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :schema header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + r->schema_start = r->header_start; + r->schema_end = r->header_end; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_host(ngx_http_request_t *r) +{ + ngx_table_elt_t *h; + + if (r->headers_in.host) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :host header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + r->headers_in.host = h; + + h->hash = r->header_hash; + + h->key.len = r->header_name_end - r->header_name_start; + h->key.data = r->header_name_start; + + h->value.len = r->header_end - r->header_start; + h->value.data = r->header_start; + + h->lowcase_key = h->key.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_path(ngx_http_request_t *r) +{ + if (r->unparsed_uri.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :path header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + r->uri_start = r->header_start; + r->uri_end = r->header_end; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid URI: \"%*s\"", + r->uri_end - r->uri_start, r->uri_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_request_uri() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_version(ngx_http_request_t *r) +{ + u_char *p, ch; + + if (r->http_protocol.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :version header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + p = r->header_start; + + if (r->header_end - p < 8 || !(ngx_str5cmp(p, 'H', 'T', 'T', 'P', '/'))) { + goto invalid; + } + + ch = *(p + 5); + + if (ch < '1' || ch > '9') { + goto invalid; + } + + r->http_major = ch - '0'; + + for (p += 6; p != r->header_end - 2; p++) { + + ch = *p; + + if (ch == '.') { + break; + } + + if (ch < '0' || ch > '9') { + goto invalid; + } + + r->http_major = r->http_major * 10 + ch - '0'; + } + + if (*p != '.') { + goto invalid; + } + + ch = *(p + 1); + + if (ch < '0' || ch > '9') { + goto invalid; + } + + r->http_minor = ch - '0'; + + for (p += 2; p != r->header_end; p++) { + + ch = *p; + + if (ch < '0' || ch > '9') { + goto invalid; + } + + r->http_minor = r->http_minor * 10 + ch - '0'; + } + + r->http_protocol.len = r->header_end - r->header_start; + r->http_protocol.data = r->header_start; + r->http_version = r->http_major * 1000 + r->http_minor; + + return NGX_OK; + +invalid: + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid http version: \"%*s\"", + r->header_end - r->header_start, r->header_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; +} + + +static ngx_int_t +ngx_http_spdy_construct_request_line(ngx_http_request_t *r) +{ + u_char *p; + + if (r->method_name.len == 0 + || r->unparsed_uri.len == 0 + || r->http_protocol.len == 0) + { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + r->request_line.len = r->method_name.len + 1 + + r->unparsed_uri.len + 1 + + r->http_protocol.len; + + p = ngx_pnalloc(r->pool, r->request_line.len + 1); + if (p == NULL) { + ngx_http_spdy_close_stream(r->spdy_stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + + *p++ = ' '; + + p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len); + + *p++ = ' '; + + ngx_memcpy(p, r->http_protocol.data, r->http_protocol.len + 1); + + /* some modules expect the space character after method name */ + r->method_name.data = r->request_line.data; + + return NGX_OK; +} + + +static void +ngx_http_spdy_run_request(ngx_http_request_t *r) +{ + ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + if (ngx_http_spdy_construct_request_line(r) != NGX_OK) { + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy http request line: \"%V\"", &r->request_line); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + part = &r->headers_in.headers.part; + h = part->elts; + + for (i = 0 ;; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + hh = ngx_hash_find(&cmcf->headers_in_hash, h[i].hash, + h[i].lowcase_key, h[i].key.len); + + if (hh && hh->handler(r, &h[i], hh->offset) != NGX_OK) { + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy http header: \"%V: %V\"", &h[i].key, &h[i].value); + } + + r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; + + if (ngx_http_process_request_header(r) != NGX_OK) { + return; + } + + if (r->headers_in.content_length_n > 0 && r->spdy_stream->in_closed) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + + r->spdy_stream->skip_data = NGX_SPDY_DATA_ERROR; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return; + } + + ngx_http_process_request(r); +} + + +static ngx_int_t +ngx_http_spdy_init_request_body(ngx_http_request_t *r) +{ + ngx_buf_t *buf; + ngx_temp_file_t *tf; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); + if (rb == NULL) { + return NGX_ERROR; + } + + r->request_body = rb; + + if (r->spdy_stream->in_closed) { + return NGX_OK; + } + + rb->rest = r->headers_in.content_length_n; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->request_body_in_file_only + || rb->rest > (off_t) clcf->client_body_buffer_size + || rb->rest < 0) + { + tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)); + if (tf == NULL) { + return NGX_ERROR; + } + + tf->file.fd = NGX_INVALID_FILE; + tf->file.log = r->connection->log; + tf->path = clcf->client_body_temp_path; + tf->pool = r->pool; + tf->warn = "a client request body is buffered to a temporary file"; + tf->log_level = r->request_body_file_log_level; + tf->persistent = r->request_body_in_persistent_file; + tf->clean = r->request_body_in_clean_file; + + if (r->request_body_file_group_access) { + tf->access = 0660; + } + + rb->temp_file = tf; + + if (r->spdy_stream->in_closed + && ngx_create_temp_file(&tf->file, tf->path, tf->pool, + tf->persistent, tf->clean, tf->access) + != NGX_OK) + { + return NGX_ERROR; + } + + buf = ngx_calloc_buf(r->pool); + if (buf == NULL) { + return NGX_ERROR; + } + + } else { + + if (rb->rest == 0) { + return NGX_OK; + } + + buf = ngx_create_temp_buf(r->pool, (size_t) rb->rest); + if (buf == NULL) { + return NGX_ERROR; + } + } + + rb->buf = buf; + + rb->bufs = ngx_alloc_chain_link(r->pool); + if (rb->bufs == NULL) { + return NGX_ERROR; + } + + rb->bufs->buf = buf; + rb->bufs->next = NULL; + + rb->rest = 0; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_spdy_read_request_body(ngx_http_request_t *r, + ngx_http_client_body_handler_pt post_handler) +{ + ngx_http_spdy_stream_t *stream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy read request body"); + + stream = r->spdy_stream; + + switch (stream->skip_data) { + + case NGX_SPDY_DATA_DISCARD: + post_handler(r); + return NGX_OK; + + case NGX_SPDY_DATA_ERROR: + if (r->headers_in.content_length_n == -1) { + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } else { + return NGX_HTTP_BAD_REQUEST; + } + + case NGX_SPDY_DATA_INTERNAL_ERROR: + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (!r->request_body->buf && ngx_http_spdy_init_request_body(r) != NGX_OK) { + stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (stream->in_closed) { + post_handler(r); + return NGX_OK; + } + + r->request_body->post_handler = post_handler; + + r->read_event_handler = ngx_http_test_reading; + r->write_event_handler = ngx_http_request_empty_handler; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_spdy_terminate_stream(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream, ngx_uint_t status) +{ + ngx_event_t *rev; + ngx_connection_t *fc; + + if (ngx_http_spdy_send_rst_stream(sc, stream->id, status, + NGX_SPDY_HIGHEST_PRIORITY) + == NGX_ERROR) + { + return NGX_ERROR; + } + + stream->out_closed = 1; + + fc = stream->request->connection; + fc->error = 1; + + rev = fc->read; + rev->handler(rev); + + return NGX_OK; +} + + +static void +ngx_http_spdy_close_stream_handler(ngx_event_t *ev) +{ + ngx_connection_t *fc; + ngx_http_request_t *r; + + fc = ev->data; + r = fc->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy close stream handler"); + + ngx_http_spdy_close_stream(r->spdy_stream, 0); +} + + +void +ngx_http_spdy_close_stream(ngx_http_spdy_stream_t *stream, ngx_int_t rc) +{ + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_spdy_stream_t **index, *s; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_connection_t *sc; + + sc = stream->connection; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy close stream %ui, queued %ui, processing %ui", + stream->id, stream->queued, sc->processing); + + fc = stream->request->connection; + + if (stream->queued) { + fc->write->handler = ngx_http_spdy_close_stream_handler; + return; + } + + if (!stream->out_closed) { + if (ngx_http_spdy_send_rst_stream(sc, stream->id, + NGX_SPDY_INTERNAL_ERROR, + stream->priority) + != NGX_OK) + { + sc->connection->error = 1; + } + } + + if (sc->stream == stream) { + sc->stream = NULL; + } + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + index = sc->streams_index + ngx_http_spdy_stream_index(sscf, stream->id); + + for ( ;; ) { + s = *index; + + if (s == NULL) { + break; + } + + if (s == stream) { + *index = s->index; + break; + } + + index = &s->index; + } + + ngx_http_free_request(stream->request, rc); + + ev = fc->read; + + if (ev->active || ev->disabled) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "fake read event was activated"); + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + ev = fc->write; + + if (ev->active || ev->disabled) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "fake write event was activated"); + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + fc->data = sc->free_fake_connections; + sc->free_fake_connections = fc; + + sc->processing--; + + if (sc->processing || sc->blocked) { + return; + } + + ev = sc->connection->read; + + ev->handler = ngx_http_spdy_handle_connection_handler; + ngx_post_event(ev, &ngx_posted_events); +} + + +static void +ngx_http_spdy_handle_connection_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + + rev->handler = ngx_http_spdy_read_handler; + + if (rev->ready) { + ngx_http_spdy_read_handler(rev); + return; + } + + c = rev->data; + + ngx_http_spdy_handle_connection(c->data); +} + + +static void +ngx_http_spdy_keepalive_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_connection_t *sc; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy keepalive handler"); + + if (rev->timedout || c->close) { + ngx_http_close_connection(c); + return; + } + +#if (NGX_HAVE_KQUEUE) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + if (rev->pending_eof) { + c->log->handler = NULL; + ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno, + "kevent() reported that client %V closed " + "keepalive connection", &c->addr_text); +#if (NGX_HTTP_SSL) + if (c->ssl) { + c->ssl->no_send_shutdown = 1; + } +#endif + ngx_http_close_connection(c); + return; + } + } + +#endif + + c->destroyed = 0; + c->idle = 0; + ngx_reusable_connection(c, 0); + + sc = c->data; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + sc->pool = ngx_create_pool(sscf->pool_size, sc->connection->log); + if (sc->pool == NULL) { + ngx_http_close_connection(c); + return; + } + + sc->streams_index = ngx_pcalloc(sc->pool, + ngx_http_spdy_streams_index_size(sscf) + * sizeof(ngx_http_spdy_stream_t *)); + if (sc->streams_index == NULL) { + ngx_http_close_connection(c); + return; + } + + c->write->handler = ngx_http_spdy_write_handler; + + rev->handler = ngx_http_spdy_read_handler; + ngx_http_spdy_read_handler(rev); +} + + +static void +ngx_http_spdy_finalize_connection(ngx_http_spdy_connection_t *sc, + ngx_int_t rc) +{ + ngx_uint_t i, size; + ngx_event_t *ev; + ngx_connection_t *c, *fc; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_srv_conf_t *sscf; + + c = sc->connection; + + if (!sc->processing) { + ngx_http_close_connection(c); + return; + } + + c->error = 1; + c->read->handler = ngx_http_empty_handler; + c->write->handler = ngx_http_empty_handler; + + sc->last_out = NULL; + + sc->blocked = 1; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + size = ngx_http_spdy_streams_index_size(sscf); + + for (i = 0; i < size; i++) { + stream = sc->streams_index[i]; + + while (stream) { + stream->handled = 0; + + r = stream->request; + fc = r->connection; + + fc->error = 1; + + if (stream->queued) { + stream->queued = 0; + + ev = fc->write; + ev->delayed = 0; + + } else { + ev = fc->read; + } + + stream = stream->index; + + ev->eof = 1; + ev->handler(ev); + } + } + + sc->blocked = 0; + + if (sc->processing) { + return; + } + + ngx_http_close_connection(c); +} + + +static ngx_int_t +ngx_http_spdy_adjust_windows(ngx_http_spdy_connection_t *sc, ssize_t delta) +{ + ngx_uint_t i, size; + ngx_event_t *wev; + ngx_http_spdy_stream_t *stream, *sn; + ngx_http_spdy_srv_conf_t *sscf; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + size = ngx_http_spdy_streams_index_size(sscf); + + for (i = 0; i < size; i++) { + + for (stream = sc->streams_index[i]; stream; stream = sn) { + sn = stream->index; + + if (delta > 0 + && stream->send_window + > (ssize_t) (NGX_SPDY_MAX_WINDOW - delta)) + { + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_FLOW_CONTROL_ERROR) + == NGX_ERROR) + { + return NGX_ERROR; + } + + continue; + } + + stream->send_window += delta; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui adjust window:%z", + stream->id, stream->send_window); + + if (stream->send_window > 0 && stream->exhausted) { + stream->exhausted = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + } + } + } + } + + return NGX_OK; +} + + +static void +ngx_http_spdy_pool_cleanup(void *data) +{ + ngx_http_spdy_connection_t *sc = data; + + if (sc->pool) { + ngx_destroy_pool(sc->pool); + } +} + + +static void * +ngx_http_spdy_zalloc(void *opaque, u_int items, u_int size) +{ + ngx_http_spdy_connection_t *sc = opaque; + + return ngx_palloc(sc->connection->pool, items * size); +} + + +static void +ngx_http_spdy_zfree(void *opaque, void *address) +{ +#if 0 + ngx_http_spdy_connection_t *sc = opaque; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy zfree: %p", address); +#endif +} diff -uNr a/src/http/ngx_http_spdy_filter_module.c b/src/http/ngx_http_spdy_filter_module.c --- a/src/http/ngx_http_spdy_filter_module.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/ngx_http_spdy_filter_module.c 2019-10-17 22:51:22.620255620 +0800 @@ -0,0 +1,1222 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include +#include + +#include + + +#define ngx_http_spdy_nv_nsize(h) (NGX_SPDY_NV_NLEN_SIZE + sizeof(h) - 1) +#define ngx_http_spdy_nv_vsize(h) (NGX_SPDY_NV_VLEN_SIZE + sizeof(h) - 1) + +#define ngx_http_spdy_nv_write_num ngx_spdy_frame_write_uint32 +#define ngx_http_spdy_nv_write_nlen ngx_spdy_frame_write_uint32 +#define ngx_http_spdy_nv_write_vlen ngx_spdy_frame_write_uint32 + +#define ngx_http_spdy_nv_write_name(p, h) \ + ngx_cpymem(ngx_http_spdy_nv_write_nlen(p, sizeof(h) - 1), h, sizeof(h) - 1) + +#define ngx_http_spdy_nv_write_val(p, h) \ + ngx_cpymem(ngx_http_spdy_nv_write_vlen(p, sizeof(h) - 1), h, sizeof(h) - 1) + + +static ngx_chain_t *ngx_http_spdy_send_chain(ngx_connection_t *fc, + ngx_chain_t *in, off_t limit); + +static ngx_inline ngx_int_t ngx_http_spdy_filter_send( + ngx_connection_t *fc, ngx_http_spdy_stream_t *stream); +static ngx_inline ngx_int_t ngx_http_spdy_flow_control( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_stream_t *stream); +static void ngx_http_spdy_waiting_queue(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream); + +static ngx_chain_t *ngx_http_spdy_filter_get_shadow( + ngx_http_spdy_stream_t *stream, ngx_buf_t *buf, off_t offset, off_t size); +static ngx_http_spdy_out_frame_t *ngx_http_spdy_filter_get_data_frame( + ngx_http_spdy_stream_t *stream, size_t len, ngx_chain_t *first, + ngx_chain_t *last); + +static ngx_int_t ngx_http_spdy_syn_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); +static ngx_int_t ngx_http_spdy_data_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); +static ngx_inline void ngx_http_spdy_handle_frame( + ngx_http_spdy_stream_t *stream, ngx_http_spdy_out_frame_t *frame); +static ngx_inline void ngx_http_spdy_handle_stream( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_stream_t *stream); + +static void ngx_http_spdy_filter_cleanup(void *data); + +static ngx_int_t ngx_http_spdy_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_spdy_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_spdy_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_spdy_filter_module = { + NGX_MODULE_V1, + &ngx_http_spdy_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; + + +static ngx_int_t +ngx_http_spdy_header_filter(ngx_http_request_t *r) +{ + int rc; + size_t len; + u_char *p, *buf, *last; + ngx_buf_t *b; + ngx_str_t host; + ngx_uint_t i, j, count, port; + ngx_chain_t *cl; + ngx_list_part_t *part, *pt; + ngx_table_elt_t *header, *h; + ngx_connection_t *c; + ngx_http_cleanup_t *cln; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_out_frame_t *frame; + ngx_http_spdy_connection_t *sc; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + u_char addr[NGX_SOCKADDR_STRLEN]; + + if (!r->spdy_stream) { + return ngx_http_next_header_filter(r); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy header filter"); + + if (r->header_sent) { + return NGX_OK; + } + + r->header_sent = 1; + + if (r != r->main) { + return NGX_OK; + } + + c = r->connection; + + if (r->method == NGX_HTTP_HEAD) { + r->header_only = 1; + } + + switch (r->headers_out.status) { + + case NGX_HTTP_OK: + case NGX_HTTP_PARTIAL_CONTENT: + break; + + case NGX_HTTP_NOT_MODIFIED: + r->header_only = 1; + break; + + case NGX_HTTP_NO_CONTENT: + r->header_only = 1; + + ngx_str_null(&r->headers_out.content_type); + + r->headers_out.content_length = NULL; + r->headers_out.content_length_n = -1; + + /* fall through */ + + default: + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + } + + len = NGX_SPDY_NV_NUM_SIZE + + ngx_http_spdy_nv_nsize(":version") + + ngx_http_spdy_nv_vsize("HTTP/1.1") + + ngx_http_spdy_nv_nsize(":status") + + (r->headers_out.status_line.len + ? NGX_SPDY_NV_VLEN_SIZE + r->headers_out.status_line.len + : ngx_http_spdy_nv_vsize("418")); + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->headers_out.server == NULL) { + len += ngx_http_spdy_nv_nsize("server"); + len += clcf->server_tokens ? ngx_http_spdy_nv_vsize(NGINX_VER) + : ngx_http_spdy_nv_vsize("nginx"); + } + + if (r->headers_out.date == NULL) { + len += ngx_http_spdy_nv_nsize("date") + + ngx_http_spdy_nv_vsize("Wed, 31 Dec 1986 10:00:00 GMT"); + } + + if (r->headers_out.content_type.len) { + len += ngx_http_spdy_nv_nsize("content-type") + + NGX_SPDY_NV_VLEN_SIZE + r->headers_out.content_type.len; + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + len += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + len += ngx_http_spdy_nv_nsize("content-length") + + NGX_SPDY_NV_VLEN_SIZE + NGX_OFF_T_LEN; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + len += ngx_http_spdy_nv_nsize("last-modified") + + ngx_http_spdy_nv_vsize("Wed, 31 Dec 1986 10:00:00 GMT"); + } + + if (r->headers_out.location + && r->headers_out.location->value.len + && r->headers_out.location->value.data[0] == '/') + { + r->headers_out.location->hash = 0; + + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.len = NGX_SOCKADDR_STRLEN; + host.data = addr; + + if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + switch (c->local_sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) c->local_sockaddr; + port = ntohs(sin6->sin6_port); + break; +#endif +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + port = 0; + break; +#endif + default: /* AF_INET */ + sin = (struct sockaddr_in *) c->local_sockaddr; + port = ntohs(sin->sin_port); + break; + } + + len += ngx_http_spdy_nv_nsize("location") + + ngx_http_spdy_nv_vsize("https://") + + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + +#if (NGX_HTTP_SSL) + if (c->ssl) + port = (port == 443) ? 0 : port; + else +#endif + port = (port == 80) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + len += sizeof(":65535") - 1; + } + + } else { + ngx_str_null(&host); + port = 0; + } + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += NGX_SPDY_NV_NLEN_SIZE + header[i].key.len + + NGX_SPDY_NV_VLEN_SIZE + header[i].value.len; + } + + buf = ngx_alloc(len, r->pool->log); + if (buf == NULL) { + return NGX_ERROR; + } + + last = buf + NGX_SPDY_NV_NUM_SIZE; + + last = ngx_http_spdy_nv_write_name(last, ":version"); + last = ngx_http_spdy_nv_write_val(last, "HTTP/1.1"); + + last = ngx_http_spdy_nv_write_name(last, ":status"); + + if (r->headers_out.status_line.len) { + last = ngx_http_spdy_nv_write_vlen(last, + r->headers_out.status_line.len); + last = ngx_cpymem(last, r->headers_out.status_line.data, + r->headers_out.status_line.len); + } else { + last = ngx_http_spdy_nv_write_vlen(last, 3); + last = ngx_sprintf(last, "%03ui", r->headers_out.status); + } + + count = 2; + + if (r->headers_out.server == NULL) { + last = ngx_http_spdy_nv_write_name(last, "server"); + last = clcf->server_tokens + ? ngx_http_spdy_nv_write_val(last, NGINX_VER) + : ngx_http_spdy_nv_write_val(last, "nginx"); + + count++; + } + + if (r->headers_out.date == NULL) { + last = ngx_http_spdy_nv_write_name(last, "date"); + + last = ngx_http_spdy_nv_write_vlen(last, ngx_cached_http_time.len); + + last = ngx_cpymem(last, ngx_cached_http_time.data, + ngx_cached_http_time.len); + + count++; + } + + if (r->headers_out.content_type.len) { + + last = ngx_http_spdy_nv_write_name(last, "content-type"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_cpymem(p, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + last = ngx_cpymem(last, "; charset=", sizeof("; charset=") - 1); + + last = ngx_cpymem(last, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* update r->headers_out.content_type for possible logging */ + + r->headers_out.content_type.len = last - p; + r->headers_out.content_type.data = p; + } + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + r->headers_out.content_type.len); + + count++; + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + last = ngx_http_spdy_nv_write_name(last, "content-length"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_sprintf(p, "%O", r->headers_out.content_length_n); + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + last - p); + + count++; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + last = ngx_http_spdy_nv_write_name(last, "last-modified"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_http_time(p, r->headers_out.last_modified_time); + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + last - p); + + count++; + } + + if (host.data) { + + last = ngx_http_spdy_nv_write_name(last, "location"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_cpymem(p, "http", sizeof("http") - 1); + +#if (NGX_HTTP_SSL) + if (c->ssl) { + *last++ ='s'; + } +#endif + + *last++ = ':'; *last++ = '/'; *last++ = '/'; + + last = ngx_cpymem(last, host.data, host.len); + + if (port) { + last = ngx_sprintf(last, ":%ui", port); + } + + last = ngx_cpymem(last, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = last - p; + r->headers_out.location->value.data = p; + ngx_str_set(&r->headers_out.location->key, "location"); + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + r->headers_out.location->value.len); + + count++; + } + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0 || header[i].hash == 2) { + continue; + } + + last = ngx_http_spdy_nv_write_nlen(last, header[i].key.len); + + ngx_strlow(last, header[i].key.data, header[i].key.len); + last += header[i].key.len; + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_cpymem(p, header[i].value.data, header[i].value.len); + + pt = part; + h = header; + + for (j = i + 1; /* void */; j++) { + + if (j >= pt->nelts) { + if (pt->next == NULL) { + break; + } + + pt = pt->next; + h = pt->elts; + j = 0; + } + + if (h[j].hash == 0 || h[j].hash == 2 + || h[j].key.len != header[i].key.len + || ngx_strncasecmp(header[i].key.data, h[j].key.data, + header[i].key.len)) + { + continue; + } + + if (h[j].value.len) { + if (last != p) { + *last++ = '\0'; + } + + last = ngx_cpymem(last, h[j].value.data, h[j].value.len); + } + + h[j].hash = 2; + } + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + last - p); + + count++; + } + + (void) ngx_http_spdy_nv_write_num(buf, count); + + stream = r->spdy_stream; + sc = stream->connection; + + len = last - buf; + + b = ngx_create_temp_buf(r->pool, NGX_SPDY_FRAME_HEADER_SIZE + + NGX_SPDY_SYN_REPLY_SIZE + + deflateBound(&sc->zstream_out, len)); + if (b == NULL) { + ngx_free(buf); + return NGX_ERROR; + } + + b->last += NGX_SPDY_FRAME_HEADER_SIZE + NGX_SPDY_SYN_REPLY_SIZE; + + sc->zstream_out.next_in = buf; + sc->zstream_out.avail_in = len; + sc->zstream_out.next_out = b->last; + sc->zstream_out.avail_out = b->end - b->last; + + rc = deflate(&sc->zstream_out, Z_SYNC_FLUSH); + + ngx_free(buf); + + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "deflate() failed: %d", rc); + return NGX_ERROR; + } + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_out.next_in, sc->zstream_out.next_out, + sc->zstream_out.avail_in, sc->zstream_out.avail_out, + rc); + + b->last = sc->zstream_out.next_out; + + p = b->pos; + p = ngx_spdy_frame_write_head(p, NGX_SPDY_SYN_REPLY); + + len = b->last - b->pos; + + r->header_size = len; + + len -= NGX_SPDY_FRAME_HEADER_SIZE; + + if (r->header_only) { + b->last_buf = 1; + p = ngx_spdy_frame_write_flags_and_len(p, NGX_SPDY_FLAG_FIN, len); + + } else { + p = ngx_spdy_frame_write_flags_and_len(p, 0, len); + } + + (void) ngx_spdy_frame_write_sid(p, stream->id); + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + frame = ngx_palloc(r->pool, sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_spdy_syn_frame_handler; + frame->stream = stream; + frame->length = len; + frame->priority = stream->priority; + frame->blocked = 1; + frame->fin = r->header_only; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0, + "spdy:%ui create SYN_REPLY frame %p: len:%uz", + stream->id, frame, frame->length); + + ngx_http_spdy_queue_blocked_frame(sc, frame); + + cln = ngx_http_cleanup_add(r, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_spdy_filter_cleanup; + cln->data = stream; + + stream->queued = 1; + + c->send_chain = ngx_http_spdy_send_chain; + c->need_last_buf = 1; + + return ngx_http_spdy_filter_send(c, stream); +} + + +static ngx_chain_t * +ngx_http_spdy_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) +{ + off_t size, offset; + size_t rest, frame_size; + ngx_chain_t *cl, *out, **ln; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_loc_conf_t *slcf; + ngx_http_spdy_out_frame_t *frame; + ngx_http_spdy_connection_t *sc; + + r = fc->data; + stream = r->spdy_stream; + +#if (NGX_SUPPRESS_WARN) + size = 0; +#endif + + while (in) { + size = ngx_buf_size(in->buf); + + if (size || in->buf->last_buf) { + break; + } + + in = in->next; + } + + if (in == NULL) { + + if (stream->queued) { + fc->write->delayed = 1; + } else { + fc->buffered &= ~NGX_SPDY_BUFFERED; + } + + return NULL; + } + + sc = stream->connection; + + if (size && ngx_http_spdy_flow_control(sc, stream) == NGX_DECLINED) { + fc->write->delayed = 1; + return in; + } + + if (limit == 0 || limit > (off_t) sc->send_window) { + limit = sc->send_window; + } + + if (limit > stream->send_window) { + limit = (stream->send_window > 0) ? stream->send_window : 0; + } + + if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) { + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = in->buf; + in->buf = cl->buf->shadow; + + offset = ngx_buf_in_memory(in->buf) + ? (cl->buf->pos - in->buf->pos) + : (cl->buf->file_pos - in->buf->file_pos); + + cl->next = stream->free_bufs; + stream->free_bufs = cl; + + } else { + offset = 0; + } + +#if (NGX_SUPPRESS_WARN) + cl = NULL; +#endif + + slcf = ngx_http_get_module_loc_conf(r, ngx_http_spdy_module); + + frame_size = (limit <= (off_t) slcf->chunk_size) ? (size_t) limit + : slcf->chunk_size; + + for ( ;; ) { + ln = &out; + rest = frame_size; + + while ((off_t) rest >= size) { + + if (offset) { + cl = ngx_http_spdy_filter_get_shadow(stream, in->buf, + offset, size); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + offset = 0; + + } else { + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = in->buf; + } + + *ln = cl; + ln = &cl->next; + + rest -= (size_t) size; + in = in->next; + + if (in == NULL) { + frame_size -= rest; + rest = 0; + break; + } + + size = ngx_buf_size(in->buf); + } + + if (rest) { + cl = ngx_http_spdy_filter_get_shadow(stream, in->buf, + offset, rest); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf->flush = 0; + cl->buf->last_buf = 0; + + *ln = cl; + + offset += rest; + size -= rest; + } + + frame = ngx_http_spdy_filter_get_data_frame(stream, frame_size, + out, cl); + if (frame == NULL) { + return NGX_CHAIN_ERROR; + } + + ngx_http_spdy_queue_frame(sc, frame); + + sc->send_window -= frame_size; + + stream->send_window -= frame_size; + stream->queued++; + + if (in == NULL) { + break; + } + + limit -= frame_size; + + if (limit == 0) { + break; + } + + if (limit < (off_t) slcf->chunk_size) { + frame_size = (size_t) limit; + } + } + + if (offset) { + cl = ngx_http_spdy_filter_get_shadow(stream, in->buf, offset, size); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + in->buf = cl->buf; + ngx_free_chain(r->pool, cl); + } + + if (ngx_http_spdy_filter_send(fc, stream) == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + if (in && ngx_http_spdy_flow_control(sc, stream) == NGX_DECLINED) { + fc->write->delayed = 1; + } + + return in; +} + + +static ngx_chain_t * +ngx_http_spdy_filter_get_shadow(ngx_http_spdy_stream_t *stream, ngx_buf_t *buf, + off_t offset, off_t size) +{ + ngx_buf_t *chunk; + ngx_chain_t *cl; + + cl = ngx_chain_get_free_buf(stream->request->pool, &stream->free_bufs); + if (cl == NULL) { + return NULL; + } + + chunk = cl->buf; + + ngx_memcpy(chunk, buf, sizeof(ngx_buf_t)); + + chunk->tag = (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow; + chunk->shadow = buf; + + if (ngx_buf_in_memory(chunk)) { + chunk->pos += offset; + chunk->last = chunk->pos + size; + } + + if (chunk->in_file) { + chunk->file_pos += offset; + chunk->file_last = chunk->file_pos + size; + } + + return cl; +} + + +static ngx_http_spdy_out_frame_t * +ngx_http_spdy_filter_get_data_frame(ngx_http_spdy_stream_t *stream, + size_t len, ngx_chain_t *first, ngx_chain_t *last) +{ + u_char *p; + ngx_buf_t *buf; + ngx_uint_t flags; + ngx_chain_t *cl; + ngx_http_spdy_out_frame_t *frame; + + + frame = stream->free_frames; + + if (frame) { + stream->free_frames = frame->next; + + } else { + frame = ngx_palloc(stream->request->pool, + sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NULL; + } + } + + flags = last->buf->last_buf ? NGX_SPDY_FLAG_FIN : 0; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0, + "spdy:%ui create DATA frame %p: len:%uz flags:%ui", + stream->id, frame, len, flags); + + cl = ngx_chain_get_free_buf(stream->request->pool, + &stream->free_data_headers); + if (cl == NULL) { + return NULL; + } + + buf = cl->buf; + + if (buf->start) { + p = buf->start; + buf->pos = p; + + p += NGX_SPDY_SID_SIZE; + + (void) ngx_spdy_frame_write_flags_and_len(p, flags, len); + + } else { + p = ngx_palloc(stream->request->pool, NGX_SPDY_FRAME_HEADER_SIZE); + if (p == NULL) { + return NULL; + } + + buf->pos = p; + buf->start = p; + + p = ngx_spdy_frame_write_sid(p, stream->id); + p = ngx_spdy_frame_write_flags_and_len(p, flags, len); + + buf->last = p; + buf->end = p; + + buf->tag = (ngx_buf_tag_t) &ngx_http_spdy_filter_get_data_frame; + buf->memory = 1; + } + + cl->next = first; + first = cl; + + last->buf->flush = 1; + + frame->first = first; + frame->last = last; + frame->handler = ngx_http_spdy_data_frame_handler; + frame->stream = stream; + frame->length = len; + frame->priority = stream->priority; + frame->blocked = 0; + frame->fin = last->buf->last_buf; + + return frame; +} + + +static ngx_inline ngx_int_t +ngx_http_spdy_filter_send(ngx_connection_t *fc, ngx_http_spdy_stream_t *stream) +{ + stream->blocked = 1; + + if (ngx_http_spdy_send_output_queue(stream->connection) == NGX_ERROR) { + fc->error = 1; + return NGX_ERROR; + } + + stream->blocked = 0; + + if (stream->queued) { + fc->buffered |= NGX_SPDY_BUFFERED; + fc->write->delayed = 1; + return NGX_AGAIN; + } + + fc->buffered &= ~NGX_SPDY_BUFFERED; + + return NGX_OK; +} + + +static ngx_inline ngx_int_t +ngx_http_spdy_flow_control(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream) +{ + if (stream->send_window <= 0) { + stream->exhausted = 1; + return NGX_DECLINED; + } + + if (sc->send_window == 0) { + ngx_http_spdy_waiting_queue(sc, stream); + return NGX_DECLINED; + } + + return NGX_OK; +} + + +static void +ngx_http_spdy_waiting_queue(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream) +{ + ngx_queue_t *q; + ngx_http_spdy_stream_t *s; + + if (stream->handled) { + return; + } + + stream->handled = 1; + + for (q = ngx_queue_last(&sc->waiting); + q != ngx_queue_sentinel(&sc->waiting); + q = ngx_queue_prev(q)) + { + s = ngx_queue_data(q, ngx_http_spdy_stream_t, queue); + + /* + * NB: higher values represent lower priorities. + */ + if (stream->priority >= s->priority) { + break; + } + } + + ngx_queue_insert_after(q, &stream->queue); +} + + +static ngx_int_t +ngx_http_spdy_syn_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + ngx_http_spdy_stream_t *stream; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + stream = frame->stream; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui SYN_REPLY frame %p was sent", stream->id, frame); + + ngx_free_chain(stream->request->pool, frame->first); + + ngx_http_spdy_handle_frame(stream, frame); + + ngx_http_spdy_handle_stream(sc, stream); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_data_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + ngx_chain_t *cl, *ln; + ngx_http_spdy_stream_t *stream; + + stream = frame->stream; + + cl = frame->first; + + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_data_frame) { + + if (cl->buf->pos != cl->buf->last) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui DATA frame %p was sent partially", + stream->id, frame); + + return NGX_AGAIN; + } + + ln = cl->next; + + cl->next = stream->free_data_headers; + stream->free_data_headers = cl; + + if (cl == frame->last) { + goto done; + } + + cl = ln; + } + + for ( ;; ) { + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) { + buf = cl->buf->shadow; + + if (ngx_buf_in_memory(buf)) { + buf->pos = cl->buf->pos; + } + + if (buf->in_file) { + buf->file_pos = cl->buf->file_pos; + } + } + + if (ngx_buf_size(cl->buf) != 0) { + + if (cl != frame->first) { + frame->first = cl; + ngx_http_spdy_handle_stream(sc, stream); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui DATA frame %p was sent partially", + stream->id, frame); + + return NGX_AGAIN; + } + + ln = cl->next; + + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) { + cl->next = stream->free_bufs; + stream->free_bufs = cl; + + } else { + ngx_free_chain(stream->request->pool, cl); + } + + if (cl == frame->last) { + goto done; + } + + cl = ln; + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui DATA frame %p was sent", stream->id, frame); + + stream->request->header_size += NGX_SPDY_FRAME_HEADER_SIZE; + + ngx_http_spdy_handle_frame(stream, frame); + + ngx_http_spdy_handle_stream(sc, stream); + + return NGX_OK; +} + + +static ngx_inline void +ngx_http_spdy_handle_frame(ngx_http_spdy_stream_t *stream, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_http_request_t *r; + + r = stream->request; + + r->connection->sent += NGX_SPDY_FRAME_HEADER_SIZE + frame->length; + + if (frame->fin) { + stream->out_closed = 1; + } + + frame->next = stream->free_frames; + stream->free_frames = frame; + + stream->queued--; +} + + +static ngx_inline void +ngx_http_spdy_handle_stream(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream) +{ + ngx_event_t *wev; + + if (stream->handled || stream->blocked || stream->exhausted) { + return; + } + + wev = stream->request->connection->write; + + /* + * This timer can only be set if the stream was delayed because of rate + * limit. In that case the event should be triggered by the timer. + */ + + if (!wev->timer_set) { + wev->delayed = 0; + + stream->handled = 1; + ngx_queue_insert_tail(&sc->posted, &stream->queue); + } +} + + +static void +ngx_http_spdy_filter_cleanup(void *data) +{ + ngx_http_spdy_stream_t *stream = data; + + size_t delta; + ngx_http_spdy_out_frame_t *frame, **fn; + ngx_http_spdy_connection_t *sc; + + if (stream->handled) { + stream->handled = 0; + ngx_queue_remove(&stream->queue); + } + + if (stream->queued == 0) { + return; + } + + delta = 0; + sc = stream->connection; + fn = &sc->last_out; + + for ( ;; ) { + frame = *fn; + + if (frame == NULL) { + break; + } + + if (frame->stream == stream && !frame->blocked) { + *fn = frame->next; + + delta += frame->length; + + if (--stream->queued == 0) { + break; + } + + continue; + } + + fn = &frame->next; + } + + if (sc->send_window == 0 && delta && !ngx_queue_empty(&sc->waiting)) { + ngx_queue_add(&sc->posted, &sc->waiting); + ngx_queue_init(&sc->waiting); + } + + sc->send_window += delta; +} + + +static ngx_int_t +ngx_http_spdy_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_spdy_header_filter; + + return NGX_OK; +} diff -uNr a/src/http/ngx_http_spdy.h b/src/http/ngx_http_spdy.h --- a/src/http/ngx_http_spdy.h 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/ngx_http_spdy.h 2019-10-17 22:51:22.620255620 +0800 @@ -0,0 +1,261 @@ +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#ifndef _NGX_HTTP_SPDY_H_INCLUDED_ +#define _NGX_HTTP_SPDY_H_INCLUDED_ + + +#include +#include +#include + +#include + + +#define NGX_SPDY_VERSION 3 + +#define NGX_SPDY_NPN_ADVERTISE "\x08spdy/3.1" +#define NGX_SPDY_NPN_NEGOTIATED "spdy/3.1" + +#define NGX_SPDY_STATE_BUFFER_SIZE 16 + +#define NGX_SPDY_CTL_BIT 1 + +#define NGX_SPDY_SYN_STREAM 1 +#define NGX_SPDY_SYN_REPLY 2 +#define NGX_SPDY_RST_STREAM 3 +#define NGX_SPDY_SETTINGS 4 +#define NGX_SPDY_PING 6 +#define NGX_SPDY_GOAWAY 7 +#define NGX_SPDY_HEADERS 8 +#define NGX_SPDY_WINDOW_UPDATE 9 + +#define NGX_SPDY_FRAME_HEADER_SIZE 8 + +#define NGX_SPDY_SID_SIZE 4 +#define NGX_SPDY_DELTA_SIZE 4 + +#define NGX_SPDY_SYN_STREAM_SIZE 10 +#define NGX_SPDY_SYN_REPLY_SIZE 4 +#define NGX_SPDY_RST_STREAM_SIZE 8 +#define NGX_SPDY_PING_SIZE 4 +#define NGX_SPDY_GOAWAY_SIZE 8 +#define NGX_SPDY_WINDOW_UPDATE_SIZE 8 +#define NGX_SPDY_NV_NUM_SIZE 4 +#define NGX_SPDY_NV_NLEN_SIZE 4 +#define NGX_SPDY_NV_VLEN_SIZE 4 +#define NGX_SPDY_SETTINGS_NUM_SIZE 4 +#define NGX_SPDY_SETTINGS_FID_SIZE 4 +#define NGX_SPDY_SETTINGS_VAL_SIZE 4 + +#define NGX_SPDY_SETTINGS_PAIR_SIZE \ + (NGX_SPDY_SETTINGS_FID_SIZE + NGX_SPDY_SETTINGS_VAL_SIZE) + +#define NGX_SPDY_HIGHEST_PRIORITY 0 +#define NGX_SPDY_LOWEST_PRIORITY 7 + +#define NGX_SPDY_FLAG_FIN 0x01 +#define NGX_SPDY_FLAG_UNIDIRECTIONAL 0x02 +#define NGX_SPDY_FLAG_CLEAR_SETTINGS 0x01 + +#define NGX_SPDY_MAX_FRAME_SIZE ((1 << 24) - 1) + +#define NGX_SPDY_DATA_DISCARD 1 +#define NGX_SPDY_DATA_ERROR 2 +#define NGX_SPDY_DATA_INTERNAL_ERROR 3 + + +typedef struct ngx_http_spdy_connection_s ngx_http_spdy_connection_t; +typedef struct ngx_http_spdy_out_frame_s ngx_http_spdy_out_frame_t; + + +typedef u_char *(*ngx_http_spdy_handler_pt) (ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); + +struct ngx_http_spdy_connection_s { + ngx_connection_t *connection; + ngx_http_connection_t *http_connection; + + ngx_uint_t processing; + + size_t send_window; + size_t recv_window; + size_t init_window; + + ngx_queue_t waiting; + + u_char buffer[NGX_SPDY_STATE_BUFFER_SIZE]; + size_t buffer_used; + ngx_http_spdy_handler_pt handler; + + z_stream zstream_in; + z_stream zstream_out; + + ngx_pool_t *pool; + + ngx_http_spdy_out_frame_t *free_ctl_frames; + ngx_connection_t *free_fake_connections; + + ngx_http_spdy_stream_t **streams_index; + + ngx_http_spdy_out_frame_t *last_out; + + ngx_queue_t posted; + + ngx_http_spdy_stream_t *stream; + + ngx_uint_t entries; + size_t length; + u_char flags; + + ngx_uint_t last_sid; + + unsigned blocked:1; + unsigned incomplete:1; +}; + + +struct ngx_http_spdy_stream_s { + ngx_uint_t id; + ngx_http_request_t *request; + ngx_http_spdy_connection_t *connection; + ngx_http_spdy_stream_t *index; + + ngx_uint_t header_buffers; + ngx_uint_t queued; + + /* + * A change to SETTINGS_INITIAL_WINDOW_SIZE could cause the + * send_window to become negative, hence it's signed. + */ + ssize_t send_window; + size_t recv_window; + + ngx_http_spdy_out_frame_t *free_frames; + ngx_chain_t *free_data_headers; + ngx_chain_t *free_bufs; + + ngx_queue_t queue; + + unsigned priority:3; + unsigned handled:1; + unsigned blocked:1; + unsigned exhausted:1; + unsigned in_closed:1; + unsigned out_closed:1; + unsigned skip_data:2; +}; + + +struct ngx_http_spdy_out_frame_s { + ngx_http_spdy_out_frame_t *next; + ngx_chain_t *first; + ngx_chain_t *last; + ngx_int_t (*handler)(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame); + + ngx_http_spdy_stream_t *stream; + size_t length; + + ngx_uint_t priority; + unsigned blocked:1; + unsigned fin:1; +}; + + +static ngx_inline void +ngx_http_spdy_queue_frame(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_http_spdy_out_frame_t **out; + + for (out = &sc->last_out; *out; out = &(*out)->next) + { + /* + * NB: higher values represent lower priorities. + */ + if (frame->priority >= (*out)->priority) { + break; + } + } + + frame->next = *out; + *out = frame; +} + + +static ngx_inline void +ngx_http_spdy_queue_blocked_frame(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_http_spdy_out_frame_t **out; + + for (out = &sc->last_out; *out; out = &(*out)->next) + { + if ((*out)->blocked) { + break; + } + } + + frame->next = *out; + *out = frame; +} + + +void ngx_http_spdy_init(ngx_event_t *rev); +void ngx_http_spdy_request_headers_init(void); + +ngx_int_t ngx_http_spdy_read_request_body(ngx_http_request_t *r, + ngx_http_client_body_handler_pt post_handler); + +void ngx_http_spdy_close_stream(ngx_http_spdy_stream_t *stream, ngx_int_t rc); + +ngx_int_t ngx_http_spdy_send_output_queue(ngx_http_spdy_connection_t *sc); + + +#define ngx_spdy_frame_aligned_write_uint16(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) + +#define ngx_spdy_frame_aligned_write_uint32(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_spdy_frame_write_uint16 ngx_spdy_frame_aligned_write_uint16 +#define ngx_spdy_frame_write_uint32 ngx_spdy_frame_aligned_write_uint32 + +#else + +#define ngx_spdy_frame_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_spdy_frame_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + + +#define ngx_spdy_ctl_frame_head(t) \ + ((uint32_t) NGX_SPDY_CTL_BIT << 31 | NGX_SPDY_VERSION << 16 | (t)) + +#define ngx_spdy_frame_write_head(p, t) \ + ngx_spdy_frame_aligned_write_uint32(p, ngx_spdy_ctl_frame_head(t)) + +#define ngx_spdy_frame_write_flags_and_len(p, f, l) \ + ngx_spdy_frame_aligned_write_uint32(p, (f) << 24 | (l)) +#define ngx_spdy_frame_write_flags_and_id(p, f, i) \ + ngx_spdy_frame_aligned_write_uint32(p, (f) << 24 | (i)) + +#define ngx_spdy_frame_write_sid ngx_spdy_frame_aligned_write_uint32 +#define ngx_spdy_frame_write_window ngx_spdy_frame_aligned_write_uint32 + +#endif /* _NGX_HTTP_SPDY_H_INCLUDED_ */ diff -uNr a/src/http/ngx_http_spdy_module.c b/src/http/ngx_http_spdy_module.c --- a/src/http/ngx_http_spdy_module.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/ngx_http_spdy_module.c 2019-10-17 22:51:22.621255628 +0800 @@ -0,0 +1,408 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include + + +static ngx_int_t ngx_http_spdy_add_variables(ngx_conf_t *cf); + +static ngx_int_t ngx_http_spdy_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_spdy_request_priority_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_http_spdy_module_init(ngx_cycle_t *cycle); + +static void *ngx_http_spdy_create_main_conf(ngx_conf_t *cf); +static char *ngx_http_spdy_init_main_conf(ngx_conf_t *cf, void *conf); +static void *ngx_http_spdy_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_spdy_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static void *ngx_http_spdy_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_spdy_merge_loc_conf(ngx_conf_t *cf, void *parent, + void *child); + +static char *ngx_http_spdy_recv_buffer_size(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_http_spdy_pool_size(ngx_conf_t *cf, void *post, void *data); +static char *ngx_http_spdy_streams_index_mask(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_http_spdy_chunk_size(ngx_conf_t *cf, void *post, void *data); + + +static ngx_conf_num_bounds_t ngx_http_spdy_headers_comp_bounds = { + ngx_conf_check_num_bounds, 0, 9 +}; + +static ngx_conf_post_t ngx_http_spdy_recv_buffer_size_post = + { ngx_http_spdy_recv_buffer_size }; +static ngx_conf_post_t ngx_http_spdy_pool_size_post = + { ngx_http_spdy_pool_size }; +static ngx_conf_post_t ngx_http_spdy_streams_index_mask_post = + { ngx_http_spdy_streams_index_mask }; +static ngx_conf_post_t ngx_http_spdy_chunk_size_post = + { ngx_http_spdy_chunk_size }; + + +static ngx_command_t ngx_http_spdy_commands[] = { + + { ngx_string("spdy_recv_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_MAIN_CONF_OFFSET, + offsetof(ngx_http_spdy_main_conf_t, recv_buffer_size), + &ngx_http_spdy_recv_buffer_size_post }, + + { ngx_string("spdy_pool_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, pool_size), + &ngx_http_spdy_pool_size_post }, + + { ngx_string("spdy_max_concurrent_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, concurrent_streams), + NULL }, + + { ngx_string("spdy_streams_index_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, streams_index_mask), + &ngx_http_spdy_streams_index_mask_post }, + + { ngx_string("spdy_recv_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, recv_timeout), + NULL }, + + { ngx_string("spdy_keepalive_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, keepalive_timeout), + NULL }, + + { ngx_string("spdy_headers_comp"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, headers_comp), + &ngx_http_spdy_headers_comp_bounds }, + + { ngx_string("spdy_chunk_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_spdy_loc_conf_t, chunk_size), + &ngx_http_spdy_chunk_size_post }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_spdy_module_ctx = { + ngx_http_spdy_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + ngx_http_spdy_create_main_conf, /* create main configuration */ + ngx_http_spdy_init_main_conf, /* init main configuration */ + + ngx_http_spdy_create_srv_conf, /* create server configuration */ + ngx_http_spdy_merge_srv_conf, /* merge server configuration */ + + ngx_http_spdy_create_loc_conf, /* create location configuration */ + ngx_http_spdy_merge_loc_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_spdy_module = { + NGX_MODULE_V1, + &ngx_http_spdy_module_ctx, /* module context */ + ngx_http_spdy_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + ngx_http_spdy_module_init, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_variable_t ngx_http_spdy_vars[] = { + + { ngx_string("spdy"), NULL, + ngx_http_spdy_variable, 0, 0, 0 }, + + { ngx_string("spdy_request_priority"), NULL, + ngx_http_spdy_request_priority_variable, 0, 0, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +static ngx_int_t +ngx_http_spdy_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_spdy_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->spdy_stream) { + v->len = sizeof("3.1") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "3.1"; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_request_priority_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->spdy_stream) { + v->len = 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + v->data = ngx_pnalloc(r->pool, 1); + if (v->data == NULL) { + return NGX_ERROR; + } + + v->data[0] = '0' + (u_char) r->spdy_stream->priority; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_module_init(ngx_cycle_t *cycle) +{ + ngx_http_spdy_request_headers_init(); + + return NGX_OK; +} + + +static void * +ngx_http_spdy_create_main_conf(ngx_conf_t *cf) +{ + ngx_http_spdy_main_conf_t *smcf; + + smcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_main_conf_t)); + if (smcf == NULL) { + return NULL; + } + + smcf->recv_buffer_size = NGX_CONF_UNSET_SIZE; + + return smcf; +} + + +static char * +ngx_http_spdy_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_http_spdy_main_conf_t *smcf = conf; + + ngx_conf_init_size_value(smcf->recv_buffer_size, 256 * 1024); + + return NGX_CONF_OK; +} + + +static void * +ngx_http_spdy_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_spdy_srv_conf_t *sscf; + + sscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_srv_conf_t)); + if (sscf == NULL) { + return NULL; + } + + sscf->pool_size = NGX_CONF_UNSET_SIZE; + + sscf->concurrent_streams = NGX_CONF_UNSET_UINT; + sscf->streams_index_mask = NGX_CONF_UNSET_UINT; + + sscf->recv_timeout = NGX_CONF_UNSET_MSEC; + sscf->keepalive_timeout = NGX_CONF_UNSET_MSEC; + + sscf->headers_comp = NGX_CONF_UNSET; + + return sscf; +} + + +static char * +ngx_http_spdy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_spdy_srv_conf_t *prev = parent; + ngx_http_spdy_srv_conf_t *conf = child; + + ngx_conf_merge_size_value(conf->pool_size, prev->pool_size, 4096); + + ngx_conf_merge_uint_value(conf->concurrent_streams, + prev->concurrent_streams, 100); + + ngx_conf_merge_uint_value(conf->streams_index_mask, + prev->streams_index_mask, 32 - 1); + + ngx_conf_merge_msec_value(conf->recv_timeout, + prev->recv_timeout, 30000); + ngx_conf_merge_msec_value(conf->keepalive_timeout, + prev->keepalive_timeout, 180000); + + ngx_conf_merge_value(conf->headers_comp, prev->headers_comp, 0); + + return NGX_CONF_OK; +} + + +static void * +ngx_http_spdy_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_spdy_loc_conf_t *slcf; + + slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_loc_conf_t)); + if (slcf == NULL) { + return NULL; + } + + slcf->chunk_size = NGX_CONF_UNSET_SIZE; + + return slcf; +} + + +static char * +ngx_http_spdy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_spdy_loc_conf_t *prev = parent; + ngx_http_spdy_loc_conf_t *conf = child; + + ngx_conf_merge_size_value(conf->chunk_size, prev->chunk_size, 8 * 1024); + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_recv_buffer_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp <= 2 * NGX_SPDY_STATE_BUFFER_SIZE) { + return "value is too small"; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_pool_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp < NGX_MIN_POOL_SIZE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the pool size must be no less than %uz", + NGX_MIN_POOL_SIZE); + return NGX_CONF_ERROR; + } + + if (*sp % NGX_POOL_ALIGNMENT) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the pool size must be a multiple of %uz", + NGX_POOL_ALIGNMENT); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_streams_index_mask(ngx_conf_t *cf, void *post, void *data) +{ + ngx_uint_t *np = data; + + ngx_uint_t mask; + + mask = *np - 1; + + if (*np == 0 || (*np & mask)) { + return "must be a power of two"; + } + + *np = mask; + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_chunk_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the spdy chunk size cannot be zero"); + return NGX_CONF_ERROR; + } + + if (*sp > NGX_SPDY_MAX_FRAME_SIZE) { + *sp = NGX_SPDY_MAX_FRAME_SIZE; + } + + return NGX_CONF_OK; +} diff -uNr a/src/http/ngx_http_spdy_module.h b/src/http/ngx_http_spdy_module.h --- a/src/http/ngx_http_spdy_module.h 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/ngx_http_spdy_module.h 2019-10-17 22:51:22.621255628 +0800 @@ -0,0 +1,41 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#ifndef _NGX_HTTP_SPDY_MODULE_H_INCLUDED_ +#define _NGX_HTTP_SPDY_MODULE_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + size_t recv_buffer_size; + u_char *recv_buffer; +} ngx_http_spdy_main_conf_t; + + +typedef struct { + size_t pool_size; + ngx_uint_t concurrent_streams; + ngx_uint_t streams_index_mask; + ngx_msec_t recv_timeout; + ngx_msec_t keepalive_timeout; + ngx_int_t headers_comp; +} ngx_http_spdy_srv_conf_t; + + +typedef struct { + size_t chunk_size; +} ngx_http_spdy_loc_conf_t; + + +extern ngx_module_t ngx_http_spdy_module; + + +#endif /* _NGX_HTTP_SPDY_MODULE_H_INCLUDED_ */ diff -uNr a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/ngx_http_upstream.c 2019-10-17 22:51:22.623255645 +0800 @@ -525,6 +525,12 @@ return; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + ngx_http_upstream_init_request(r); + return; + } +#endif if (c->read->timer_set) { ngx_del_timer(c->read); @@ -1347,6 +1353,11 @@ return; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + return; + } +#endif #if (NGX_HAVE_KQUEUE) diff -uNr a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c --- a/src/http/v2/ngx_http_v2.c 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/v2/ngx_http_v2.c 2019-10-17 22:51:22.625255661 +0800 @@ -270,6 +270,8 @@ h2c->frame_size = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + h2c->max_hpack_table_size = NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE; + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); h2c->concurrent_pushes = h2scf->concurrent_pushes; @@ -2092,6 +2094,14 @@ case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING: h2c->table_update = 1; + + if (value > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { + h2c->max_hpack_table_size = NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE; + } else { + h2c->max_hpack_table_size = value; + } + + h2c->indicate_resize = 1; break; default: diff -uNr a/src/http/v2/ngx_http_v2_encode.c b/src/http/v2/ngx_http_v2_encode.c --- a/src/http/v2/ngx_http_v2_encode.c 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_encode.c 2019-10-17 22:51:22.625255661 +0800 @@ -10,7 +10,7 @@ #include -static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, +u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); @@ -40,7 +40,7 @@ } -static u_char * +u_char * ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) { if (value < prefix) { diff -uNr a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c --- a/src/http/v2/ngx_http_v2_filter_module.c 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_filter_module.c 2019-10-17 22:51:22.626255669 +0800 @@ -23,10 +23,53 @@ #define ngx_http_v2_literal_size(h) \ (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1) +#define ngx_http_v2_indexed(i) (128 + (i)) +#define ngx_http_v2_inc_indexed(i) (64 + (i)) + +#define NGX_HTTP_V2_ENCODE_RAW 0 +#define NGX_HTTP_V2_ENCODE_HUFF 0x80 + +#define NGX_HTTP_V2_AUTHORITY_INDEX 1 +#define NGX_HTTP_V2_METHOD_GET_INDEX 2 +#define NGX_HTTP_V2_PATH_INDEX 4 + +#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6 +#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7 + +#define NGX_HTTP_V2_STATUS_INDEX 8 +#define NGX_HTTP_V2_STATUS_200_INDEX 8 +#define NGX_HTTP_V2_STATUS_204_INDEX 9 +#define NGX_HTTP_V2_STATUS_206_INDEX 10 +#define NGX_HTTP_V2_STATUS_304_INDEX 11 +#define NGX_HTTP_V2_STATUS_400_INDEX 12 +#define NGX_HTTP_V2_STATUS_404_INDEX 13 +#define NGX_HTTP_V2_STATUS_500_INDEX 14 + +#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 +#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 +#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 +#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 +#define NGX_HTTP_V2_DATE_INDEX 33 +#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 +#define NGX_HTTP_V2_LOCATION_INDEX 46 +#define NGX_HTTP_V2_SERVER_INDEX 54 +#define NGX_HTTP_V2_USER_AGENT_INDEX 58 +#define NGX_HTTP_V2_VARY_INDEX 59 #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 +static const struct { + u_char *name; + u_char const len; +} push_header[] = { + { (u_char*)":authority" , 10 }, + { (u_char*)"accept-encoding" , 15 }, + { (u_char*)"accept-language" , 15 }, + { (u_char*)"user-agent" , 10 } +}; + + typedef struct { ngx_str_t name; u_char index; @@ -155,11 +198,9 @@ #endif static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER); - static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)]; static size_t nginx_ver_build_len = ngx_http_v2_literal_size(NGINX_VER_BUILD); - static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)]; stream = r->stream; @@ -435,7 +476,7 @@ } tmp = ngx_palloc(r->pool, tmp_len); - pos = ngx_pnalloc(r->pool, len); + pos = ngx_pnalloc(r->pool, len + 15 + 1); if (pos == NULL || tmp == NULL) { return NGX_ERROR; @@ -443,11 +484,16 @@ start = pos; - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; + h2c = r->stream->connection; + + if (h2c->indicate_resize) { + *pos = 32; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), + h2c->max_hpack_table_size); + h2c->indicate_resize = 0; +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_table_resize(h2c); +#endif } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, @@ -458,67 +504,28 @@ *pos++ = status; } else { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); - *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3; - pos = ngx_sprintf(pos, "%03ui", r->headers_out.status); + ngx_sprintf(pos + 8, "%O3ui", r->headers_out.status); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)":status", + sizeof(":status") - 1, pos + 8, 3, tmp); } if (r->headers_out.server == NULL) { - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: %s\"", - NGINX_VER); - - } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: %s\"", - NGINX_VER_BUILD); - - } else { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: nginx\""); - } - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); - - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - if (nginx_ver[0] == '\0') { - p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER, - sizeof(NGINX_VER) - 1, tmp); - nginx_ver_len = p - nginx_ver; - } - - pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len); + pos = ngx_http_v2_write_header_str("server", NGINX_VER); } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - if (nginx_ver_build[0] == '\0') { - p = ngx_http_v2_write_value(nginx_ver_build, - (u_char *) NGINX_VER_BUILD, - sizeof(NGINX_VER_BUILD) - 1, tmp); - nginx_ver_build_len = p - nginx_ver_build; - } - - pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len); + pos = ngx_http_v2_write_header_str("server", NGINX_VER_BUILD); } else { - pos = ngx_cpymem(pos, nginx, sizeof(nginx)); + pos = ngx_http_v2_write_header_str("server", "nginx"); } } if (r->headers_out.date == NULL) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"date: %V\"", - &ngx_cached_http_time); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); - pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data, - ngx_cached_http_time.len, tmp); + pos = ngx_http_v2_write_header_tbl("date", ngx_cached_http_time); } if (r->headers_out.content_type.len) { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); - if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { @@ -544,64 +551,36 @@ r->headers_out.content_type.data = p - len; } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"content-type: %V\"", - &r->headers_out.content_type); - - pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data, - r->headers_out.content_type.len, tmp); + pos = ngx_http_v2_write_header_tbl("content-type", + r->headers_out.content_type); } if (r->headers_out.content_length == NULL && r->headers_out.content_length_n >= 0) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"content-length: %O\"", - r->headers_out.content_length_n); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX); - - p = pos; - pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n); - *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1); + p = ngx_sprintf(pos + 15, "%O", r->headers_out.content_length_n); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"content-length", + sizeof("content-length") - 1, pos + 15, + p - (pos + 15), tmp); } if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); - - ngx_http_time(pos, r->headers_out.last_modified_time); + ngx_http_time(pos + 14, r->headers_out.last_modified_time); len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"last-modified: %*s\"", - len, pos); - - /* - * Date will always be encoded using huffman in the temporary buffer, - * so it's safe here to use src and dst pointing to the same address. - */ - pos = ngx_http_v2_write_value(pos, pos, len, tmp); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"last-modified", + sizeof("last-modified") - 1, pos + 14, + len, tmp); } if (r->headers_out.location && r->headers_out.location->value.len) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"location: %V\"", - &r->headers_out.location->value); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); - pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data, - r->headers_out.location->value.len, tmp); + pos = ngx_http_v2_write_header_tbl("location", r->headers_out.location->value); } #if (NGX_HTTP_GZIP) if (r->gzip_vary) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"vary: Accept-Encoding\""); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); - pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding)); + pos = ngx_http_v2_write_header_str("vary", "Accept-Encoding"); } #endif @@ -624,23 +603,10 @@ continue; } -#if (NGX_DEBUG) - if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { - ngx_strlow(tmp, header[i].key.data, header[i].key.len); - - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"%*s: %V\"", - header[i].key.len, tmp, &header[i].value); - } -#endif - - *pos++ = 0; - - pos = ngx_http_v2_write_name(pos, header[i].key.data, - header[i].key.len, tmp); + pos = ngx_http_v2_write_header(h2c, pos, header[i].key.data, + header[i].key.len, header[i].value.data, + header[i].value.len, tmp); - pos = ngx_http_v2_write_value(pos, header[i].value.data, - header[i].value.len, tmp); } fin = r->header_only @@ -998,6 +964,7 @@ for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { len += binary[i].len; + len += push_header[i].len + 1; } pos = ngx_pnalloc(r->pool, len); @@ -1007,12 +974,17 @@ start = pos; - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; - } + h2c = r->stream->connection; + + if (h2c->indicate_resize) { + *pos = 32; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), + h2c->max_hpack_table_size); + h2c->indicate_resize = 0; +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_table_resize(h2c); +#endif + } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":method: GET\""); @@ -1022,8 +994,7 @@ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":path: %V\"", path); - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); - pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp); + pos = ngx_http_v2_write_header_pot(":path", path); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":scheme: %V\"", &r->schema); @@ -1048,11 +1019,15 @@ continue; } + value = &(*h)->value; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \"%V: %V\"", &ph[i].name, &(*h)->value); - pos = ngx_cpymem(pos, binary[i].data, binary[i].len); + pos = ngx_http_v2_write_header(h2c, pos, + push_header[i].name, push_header[i].len, value->data, value->len, + tmp); } frame = ngx_http_v2_create_push_frame(r, start, pos); diff -uNr a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h --- a/src/http/v2/ngx_http_v2.h 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/v2/ngx_http_v2.h 2019-10-17 22:51:22.627255677 +0800 @@ -52,6 +52,14 @@ #define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1) #define NGX_HTTP_V2_DEFAULT_WINDOW 65535 +#define HPACK_ENC_HTABLE_SZ 128 /* better to keep a PoT < 64k */ +#define HPACK_ENC_HTABLE_ENTRIES ((HPACK_ENC_HTABLE_SZ * 100) / 128) +#define HPACK_ENC_DYNAMIC_KEY_TBL_SZ 10 /* 10 is sufficient for most */ +#define HPACK_ENC_MAX_ENTRY 512 /* longest header size to match */ + +#define NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE 4096 +#define NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE 16384 /* < 64k */ + #define NGX_HTTP_V2_DEFAULT_WEIGHT 16 @@ -115,6 +123,46 @@ } ngx_http_v2_hpack_t; +#if (NGX_HTTP_V2_HPACK_ENC) +typedef struct { + uint64_t hash_val; + uint32_t index; + uint16_t pos; + uint16_t klen, vlen; + uint16_t size; + uint16_t next; +} ngx_http_v2_hpack_enc_entry_t; + + +typedef struct { + uint64_t hash_val; + uint32_t index; + uint16_t pos; + uint16_t klen; +} ngx_http_v2_hpack_name_entry_t; + + +typedef struct { + size_t size; /* size as defined in RFC 7541 */ + uint32_t top; /* the last entry */ + uint32_t pos; + uint16_t n_elems; /* number of elements */ + uint16_t base; /* index of the oldest entry */ + uint16_t last; /* index of the newest entry */ + + /* hash table for dynamic entries, instead using a generic hash table, + which would be too slow to process a significant amount of headers, + this table is not determenistic, and might ocasionally fail to insert + a value, at the cost of slightly worse compression, but significantly + faster performance */ + ngx_http_v2_hpack_enc_entry_t htable[HPACK_ENC_HTABLE_SZ]; + ngx_http_v2_hpack_name_entry_t heads[HPACK_ENC_DYNAMIC_KEY_TBL_SZ]; + u_char storage[NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE + + HPACK_ENC_MAX_ENTRY]; +} ngx_http_v2_hpack_enc_t; +#endif + + struct ngx_http_v2_connection_s { ngx_connection_t *connection; ngx_http_connection_t *http_connection; @@ -136,6 +184,8 @@ size_t frame_size; + size_t max_hpack_table_size; + ngx_queue_t waiting; ngx_http_v2_state_t state; @@ -163,6 +213,11 @@ unsigned blocked:1; unsigned goaway:1; unsigned push_disabled:1; + unsigned indicate_resize:1; + +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_hpack_enc_t hpack_enc; +#endif }; @@ -206,6 +261,8 @@ ngx_array_t *cookies; + size_t header_limit; + ngx_pool_t *pool; unsigned waiting:1; @@ -418,4 +475,35 @@ u_char *tmp, ngx_uint_t lower); +u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, + u_char *tmp, ngx_uint_t lower); + +u_char * +ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); + +#define ngx_http_v2_write_name(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 1) +#define ngx_http_v2_write_value(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 0) + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, u_char *value, size_t value_len, + u_char *tmp); + +void +ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c); + +#define ngx_http_v2_write_header_str(key, value) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + (u_char *) value, sizeof(value) - 1, tmp); + +#define ngx_http_v2_write_header_tbl(key, val) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + val.data, val.len, tmp); + +#define ngx_http_v2_write_header_pot(key, val) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + val->data, val->len, tmp); + #endif /* _NGX_HTTP_V2_H_INCLUDED_ */ diff -uNr a/src/http/v2/ngx_http_v2_module.c b/src/http/v2/ngx_http_v2_module.c --- a/src/http/v2/ngx_http_v2_module.c 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_module.c 2019-10-17 22:51:22.634255735 +0800 @@ -36,8 +36,6 @@ static char *ngx_http_v2_streams_index_mask(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_v2_chunk_size(ngx_conf_t *cf, void *post, void *data); -static char *ngx_http_v2_spdy_deprecated(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); static ngx_conf_post_t ngx_http_v2_recv_buffer_size_post = @@ -152,62 +150,6 @@ 0, NULL }, - { ngx_string("spdy_recv_buffer_size"), - NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_MAIN_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_pool_size"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_max_concurrent_streams"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_streams_index_size"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_recv_timeout"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_keepalive_timeout"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_headers_comp"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_chunk_size"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_LOC_CONF_OFFSET, - 0, - NULL }, - ngx_null_command }; @@ -597,14 +539,3 @@ return NGX_CONF_OK; } - - -static char * -ngx_http_v2_spdy_deprecated(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "invalid directive \"%V\": ngx_http_spdy_module " - "was superseded by ngx_http_v2_module", &cmd->name); - - return NGX_CONF_OK; -} diff -uNr a/src/http/v2/ngx_http_v2_table.c b/src/http/v2/ngx_http_v2_table.c --- a/src/http/v2/ngx_http_v2_table.c 2019-09-24 23:08:48.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_table.c 2019-10-17 22:51:22.637255759 +0800 @@ -361,3 +361,434 @@ return NGX_OK; } + + +#if (NGX_HTTP_V2_HPACK_ENC) + +static ngx_int_t +hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len); + +static ngx_int_t +hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, + uint8_t *key, size_t key_len); + + +void +ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c) +{ + ngx_http_v2_hpack_enc_entry_t *table; + uint64_t idx; + + table = h2c->hpack_enc.htable; + + while (h2c->hpack_enc.size > h2c->max_hpack_table_size) { + idx = h2c->hpack_enc.base; + h2c->hpack_enc.base = table[idx].next; + h2c->hpack_enc.size -= table[idx].size; + table[idx].hash_val = 0; + h2c->hpack_enc.n_elems--; + } +} + + +/* checks if a header is in the hpack table - if so returns the table entry, + otherwise encodes and inserts into the table and returns 0, + if failed to insert into table, returns -1 */ +static ngx_int_t +ngx_http_v2_table_encode_strings(ngx_http_v2_connection_t *h2c, + size_t key_len, size_t val_len, uint8_t *key, uint8_t *val, + ngx_int_t *header_idx) +{ + uint64_t hash_val, key_hash, idx, lru; + int i; + size_t size = key_len + val_len + 32; + uint8_t *storage = h2c->hpack_enc.storage; + + ngx_http_v2_hpack_enc_entry_t *table; + ngx_http_v2_hpack_name_entry_t *name; + + *header_idx = NGX_ERROR; + /* step 1: compute the hash value of header */ + if (size > HPACK_ENC_MAX_ENTRY || size > h2c->max_hpack_table_size) { + return NGX_ERROR; + } + + key_hash = ngx_murmur_hash2_64(key, key_len, 0x01234); + hash_val = ngx_murmur_hash2_64(val, val_len, key_hash); + + if (hash_val == 0) { + return NGX_ERROR; + } + + /* step 2: check if full header in the table */ + idx = hash_val; + i = -1; + while (idx) { + /* at most 8 locations are checked, but most will be done in 1 or 2 */ + table = &h2c->hpack_enc.htable[idx % HPACK_ENC_HTABLE_SZ]; + if (table->hash_val == hash_val + && table->klen == key_len + && table->vlen == val_len + && ngx_memcmp(key, storage + table->pos, key_len) == 0 + && ngx_memcmp(val, storage + table->pos + key_len, val_len) == 0) + { + return (h2c->hpack_enc.top - table->index) + 61; + } + + if (table->hash_val == 0 && i == -1) { + i = idx % HPACK_ENC_HTABLE_SZ; + break; + } + + idx >>= 8; + } + + /* step 3: check if key is in one of the tables */ + *header_idx = hpack_get_static_index(h2c, key, key_len); + + if (i == -1) { + return NGX_ERROR; + } + + if (*header_idx == NGX_ERROR) { + *header_idx = hpack_get_dynamic_index(h2c, key_hash, key, key_len); + } + + /* step 4: store the new entry */ + table = h2c->hpack_enc.htable; + + if (h2c->hpack_enc.top == 0xffffffff) { + /* just to be on the safe side, avoid overflow */ + ngx_memset(&h2c->hpack_enc, 0, sizeof(ngx_http_v2_hpack_enc_t)); + } + + while ((h2c->hpack_enc.size + size > h2c->max_hpack_table_size) + || h2c->hpack_enc.n_elems == HPACK_ENC_HTABLE_ENTRIES) { + /* make space for the new entry first */ + idx = h2c->hpack_enc.base; + h2c->hpack_enc.base = table[idx].next; + h2c->hpack_enc.size -= table[idx].size; + table[idx].hash_val = 0; + h2c->hpack_enc.n_elems--; + } + + table[i] = (ngx_http_v2_hpack_enc_entry_t){.hash_val = hash_val, + .index = h2c->hpack_enc.top, + .pos = h2c->hpack_enc.pos, + .klen = key_len, + .vlen = val_len, + .size = size, + .next = 0}; + + table[h2c->hpack_enc.last].next = i; + if (h2c->hpack_enc.n_elems == 0) { + h2c->hpack_enc.base = i; + } + + h2c->hpack_enc.last = i; + h2c->hpack_enc.top++; + h2c->hpack_enc.size += size; + h2c->hpack_enc.n_elems++; + + /* update header name lookup */ + if (*header_idx == NGX_ERROR ) { + lru = h2c->hpack_enc.top; + + for (i=0; ihpack_enc.heads[i]; + + if ( name->hash_val == 0 || (name->hash_val == key_hash + && ngx_memcmp(storage + name->pos, key, key_len) == 0) ) + { + name->hash_val = key_hash; + name->pos = h2c->hpack_enc.pos; + name->index = h2c->hpack_enc.top - 1; + break; + } + + if (lru > name->index) { + lru = name->index; + idx = i; + } + } + + if (i == HPACK_ENC_DYNAMIC_KEY_TBL_SZ) { + name = &h2c->hpack_enc.heads[idx]; + name->hash_val = hash_val; + name->pos = h2c->hpack_enc.pos; + name->index = h2c->hpack_enc.top - 1; + } + } + + ngx_memcpy(storage + h2c->hpack_enc.pos, key, key_len); + ngx_memcpy(storage + h2c->hpack_enc.pos + key_len, val, val_len); + + h2c->hpack_enc.pos += size; + if (h2c->hpack_enc.pos > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { + h2c->hpack_enc.pos = 0; + } + + return NGX_OK; +} + + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, + u_char *value, size_t value_len, + u_char *tmp) +{ + ngx_int_t idx, header_idx; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 output header: %*s: %*s", key_len, key, value_len, + value); + + /* attempt to find the value in the dynamic table */ + idx = ngx_http_v2_table_encode_strings(h2c, key_len, value_len, key, value, + &header_idx); + + if (idx > 0) { + /* positive index indicates success */ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Indexed Header Field: %ud", idx); + + *pos = 128; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), idx); + + } else { + + if (header_idx == NGX_ERROR) { /* if key is not present */ + + if (idx == NGX_ERROR) { /* if header was not added */ + *pos++ = 0; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field without" + " Indexing — New Name"); + } else { /* if header was added */ + *pos++ = 64; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field with " + "Incremental Indexing — New Name"); + } + + pos = ngx_http_v2_write_name(pos, key, key_len, tmp); + + } else { /* if key is present */ + + if (idx == NGX_ERROR) { + *pos = 0; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(4), header_idx); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field without" + " Indexing — Indexed Name: %ud", header_idx); + } else { + *pos = 64; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(6), header_idx); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field with " + "Incremental Indexing — Indexed Name: %ud", header_idx); + } + } + + pos = ngx_http_v2_write_value(pos, value, value_len, tmp); + } + + return pos; +} + + +static ngx_int_t +hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, + uint8_t *key, size_t key_len) +{ + ngx_http_v2_hpack_name_entry_t *name; + int i; + + for (i=0; ihpack_enc.heads[i]; + + if (name->hash_val == key_hash + && ngx_memcmp(h2c->hpack_enc.storage + name->pos, key, key_len) == 0) + { + if (name->index >= h2c->hpack_enc.top - h2c->hpack_enc.n_elems) { + return (h2c->hpack_enc.top - name->index) + 61; + } + break; + } + } + + return NGX_ERROR; +} + + +/* decide if a given header is present in the static dictionary, this could be + done in several ways, but it seems the fastest one is "exhaustive" search */ +static ngx_int_t +hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len) +{ + /* the static dictionary of response only headers, + although response headers can be put by origin, + that would be rare */ + static const struct { + u_char len; + const u_char val[28]; + u_char idx; + } server_headers[] = { + { 3, "age", 21},//0 + { 3, "via", 60}, + { 4, "date", 33},//2 + { 4, "etag", 34}, + { 4, "link", 45}, + { 4, "vary", 59}, + { 5, "allow", 22},//6 + { 6, "server", 54},//7 + { 7, "expires", 36},//8 + { 7, "refresh", 52}, + { 8, "location", 46},//10 + {10, "set-cookie", 55},//11 + {11, "retry-after", 53},//12 + {12, "content-type", 31},//13 + {13, "content-range", 30},//14 + {13, "accept-ranges", 18}, + {13, "cache-control", 24}, + {13, "last-modified", 44}, + {14, "content-length", 28},//18 + {16, "content-encoding", 26},//19 + {16, "content-language", 27}, + {16, "content-location", 29}, + {16, "www-authenticate", 61}, + {17, "transfer-encoding", 57},//23 + {18, "proxy-authenticate", 48},//24 + {19, "content-disposition", 25},//25 + {25, "strict-transport-security", 56},//26 + {27, "access-control-allow-origin", 20},//27 + {99, "", 99}, + }, *header; + + /* for a given length, where to start the search + since minimal length is 3, the table has a -3 + offset */ + static const int8_t start_at[] = { + [3-3] = 0, + [4-3] = 2, + [5-3] = 6, + [6-3] = 7, + [7-3] = 8, + [8-3] = 10, + [9-3] = -1, + [10-3] = 11, + [11-3] = 12, + [12-3] = 13, + [13-3] = 14, + [14-3] = 18, + [15-3] = -1, + [16-3] = 19, + [17-3] = 23, + [18-3] = 24, + [19-3] = 25, + [20-3] = -1, + [21-3] = -1, + [22-3] = -1, + [23-3] = -1, + [24-3] = -1, + [25-3] = 26, + [26-3] = -1, + [27-3] = 27, + }; + + uint64_t pref; + size_t save_len = len, i; + int8_t start; + + /* early exit for out of bounds lengths */ + if (len < 3 || len > 27) { + return NGX_ERROR; + } + + start = start_at[len - 3]; + if (start == -1) { + /* exit for non existent lengths */ + return NGX_ERROR; + } + + header = &server_headers[start_at[len - 3]]; + + /* load first 8 bytes of key, for fast comparison */ + if (len < 8) { + pref = 0; + if (len >= 4) { + pref = *(uint32_t *)(val + len - 4) | 0x20202020; + len -= 4; + } + while (len > 0) { /* 3 iterations at most */ + pref = (pref << 8) ^ (val[len - 1] | 0x20); + len--; + } + } else { + pref = *(uint64_t *)val | 0x2020202020202020; + len -= 8; + } + + /* iterate over headers with the right length */ + while (header->len == save_len) { + /* quickly compare the first 8 bytes, most tests will end here */ + if (pref != *(uint64_t *) header->val) { + header++; + continue; + } + + if (len == 0) { + /* len == 0, indicates prefix held the entire key */ + return header->idx; + } + /* for longer keys compare the rest */ + i = 1 + (save_len + 7) % 8; /* align so we can compare in quadwords */ + + while (i + 8 <= save_len) { /* 3 iterations at most */ + if ( *(uint64_t *)&header->val[i] + != (*(uint64_t *) &val[i]| 0x2020202020202020) ) + { + header++; + i = 0; + break; + } + i += 8; + } + + if (i == 0) { + continue; + } + + /* found the corresponding entry in the static dictionary */ + return header->idx; + } + + return NGX_ERROR; +} + +#else + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, + u_char *value, size_t value_len, + u_char *tmp) +{ + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 output header: %*s: %*s", key_len, key, value_len, + value); + + *pos++ = 64; + pos = ngx_http_v2_write_name(pos, key, key_len, tmp); + pos = ngx_http_v2_write_value(pos, value, value_len, tmp); + + return pos; +} + +#endif ================================================ FILE: nginx_with_spdy_quic.patch ================================================ Add SPDY Support. Add HTTP3(QUIC) Support. Add HTTP2 HPACK Encoding Support. Add Dynamic TLS Record support. Using: patch -p1 < nginx.patch diff -uNr a/auto/lib/conf b/auto/lib/conf --- a/auto/lib/conf 2019-12-24 23:00:09.000000000 +0800 +++ b/auto/lib/conf 2020-01-02 21:25:20.000000000 +0800 @@ -25,6 +25,10 @@ . auto/lib/openssl/conf fi +if [ $USE_QUICHE = YES ]; then + . auto/lib/quiche/conf +fi + if [ $USE_ZLIB = YES ]; then . auto/lib/zlib/conf fi diff -uNr a/auto/lib/make b/auto/lib/make --- a/auto/lib/make 2019-12-24 23:00:09.000000000 +0800 +++ b/auto/lib/make 2020-01-02 21:25:20.000000000 +0800 @@ -11,6 +11,10 @@ . auto/lib/openssl/make fi +if [ $QUICHE != NONE -a $QUICHE != NO -a $QUICHE != YES ]; then + . auto/lib/quiche/make +fi + if [ $ZLIB != NONE -a $ZLIB != NO -a $ZLIB != YES ]; then . auto/lib/zlib/make fi diff -uNr a/auto/lib/openssl/make b/auto/lib/openssl/make --- a/auto/lib/openssl/make 2019-12-24 23:00:09.000000000 +0800 +++ b/auto/lib/openssl/make 2020-01-02 21:25:20.000000000 +0800 @@ -49,11 +49,13 @@ cat << END >> $NGX_MAKEFILE $OPENSSL/.openssl/include/openssl/ssl.h: $NGX_MAKEFILE - cd $OPENSSL \\ - && if [ -f Makefile ]; then \$(MAKE) clean; fi \\ - && ./config --prefix=$ngx_prefix no-shared no-threads $OPENSSL_OPT \\ - && \$(MAKE) \\ - && \$(MAKE) install_sw LIBDIR=lib + mkdir -p $OPENSSL/build $OPENSSL/.openssl/lib $OPENSSL/.openssl/include/openssl \\ + && cd $OPENSSL/build \\ + && cmake -DCMAKE_C_FLAGS="$OPENSSL_OPT" -DCMAKE_CXX_FLAGS="$OPENSSL_OPT" .. \\ + && \$(MAKE) VERBOSE=1 \\ + && cd .. \\ + && cp -r include/openssl/*.h .openssl/include/openssl \\ + && cp build/ssl/libssl.a build/crypto/libcrypto.a .openssl/lib END diff -uNr a/auto/lib/quiche/conf b/auto/lib/quiche/conf --- a/auto/lib/quiche/conf 1970-01-01 08:00:00.000000000 +0800 +++ b/auto/lib/quiche/conf 2020-01-02 21:25:20.000000000 +0800 @@ -0,0 +1,19 @@ + +# Copyright (C) Cloudflare, Inc. + + +if [ $QUICHE != NONE ]; then + + have=NGX_QUIC . auto/have + + QUICHE_BUILD_TARGET="release" + + if [ $NGX_DEBUG = YES ]; then + QUICHE_BUILD_TARGET="debug" + fi + + CORE_INCS="$CORE_INCS $QUICHE/include" + CORE_DEPS="$CORE_DEPS $QUICHE/target/$QUICHE_BUILD_TARGET/libquiche.a" + CORE_LIBS="$CORE_LIBS $QUICHE/target/$QUICHE_BUILD_TARGET/libquiche.a $NGX_LIBPTHREAD" + +fi diff -uNr a/auto/lib/quiche/make b/auto/lib/quiche/make --- a/auto/lib/quiche/make 1970-01-01 08:00:00.000000000 +0800 +++ b/auto/lib/quiche/make 2020-01-02 21:25:20.000000000 +0800 @@ -0,0 +1,22 @@ + +# Copyright (C) Cloudflare, Inc. + + +# Default is release build +QUICHE_BUILD_FLAGS="--release --no-default-features" +QUICHE_BUILD_TARGET="release" + +if [ $NGX_DEBUG = YES ]; then + QUICHE_BUILD_FLAGS="--no-default-features" + QUICHE_BUILD_TARGET="debug" +fi + + +cat << END >> $NGX_MAKEFILE + +$QUICHE/target/$QUICHE_BUILD_TARGET/libquiche.a: \\ + $OPENSSL/.openssl/include/openssl/ssl.h \\ + $NGX_MAKEFILE + cd $QUICHE && cargo build $QUICHE_BUILD_FLAGS + +END diff -uNr a/auto/make b/auto/make --- a/auto/make 2019-12-24 23:00:09.000000000 +0800 +++ b/auto/make 2020-01-02 21:25:20.000000000 +0800 @@ -7,7 +7,8 @@ mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \ $NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \ - $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \ + $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \ + $NGX_OBJS/src/http/modules \ $NGX_OBJS/src/http/modules/perl \ $NGX_OBJS/src/mail \ $NGX_OBJS/src/stream \ diff -uNr a/auto/modules b/auto/modules --- a/auto/modules 2019-12-24 23:00:09.000000000 +0800 +++ b/auto/modules 2020-01-03 10:50:12.000000000 +0800 @@ -118,7 +118,9 @@ # ngx_http_write_filter # ngx_http_header_filter # ngx_http_chunked_filter + # ngx_http_spdy_filter # ngx_http_v2_filter + # ngx_http_v3_filter # ngx_http_range_header_filter # ngx_http_gzip_filter # ngx_http_postpone_filter @@ -150,7 +152,9 @@ ngx_http_write_filter_module \ ngx_http_header_filter_module \ ngx_http_chunked_filter_module \ + ngx_http_spdy_filter_module \ ngx_http_v2_filter_module \ + ngx_http_v3_filter_module \ ngx_http_range_header_filter_module \ ngx_http_gzip_filter_module \ ngx_http_postpone_filter_module \ @@ -201,6 +205,19 @@ . auto/module fi + if [ $HTTP_SPDY = YES ]; then + have=NGX_HTTP_SPDY . auto/have + USE_ZLIB=YES + ngx_module_name=ngx_http_spdy_filter_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs=src/http/ngx_http_spdy_filter_module.c + ngx_module_libs= + ngx_module_link=$HTTP_SPDY + + . auto/module + fi + if [ $HTTP_V2 = YES ]; then ngx_module_name=ngx_http_v2_filter_module ngx_module_incs= @@ -212,6 +229,17 @@ . auto/module fi + if [ $HTTP_V3 = YES ]; then + ngx_module_name=ngx_http_v3_filter_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs=src/http/v3/ngx_http_v3_filter_module.c + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + if :; then ngx_module_name=ngx_http_range_header_filter_module ngx_module_incs= @@ -403,6 +431,19 @@ ngx_module_type=HTTP + if [ $HTTP_SPDY = YES ]; then + have=NGX_HTTP_SPDY . auto/have + ngx_module_name=ngx_http_spdy_module + ngx_module_incs=src/http + ngx_module_deps="src/http/ngx_http_spdy.h src/http/ngx_http_spdy_module.h" + ngx_module_srcs="src/http/ngx_http_spdy.c \ + src/http/ngx_http_spdy_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_SPDY + + . auto/module + fi + if [ $HTTP_V2 = YES ]; then have=NGX_HTTP_V2 . auto/have have=NGX_HTTP_HEADERS . auto/have @@ -423,6 +464,28 @@ . auto/module fi + if [ $HTTP_V3 = YES ]; then + USE_QUICHE=YES + USE_OPENSSL=YES + have=NGX_HTTP_V3 . auto/have + have=NGX_HTTP_HEADERS . auto/have + + ngx_module_name=ngx_http_v3_module + ngx_module_incs=src/http/v3 + ngx_module_deps="src/http/v3/ngx_http_v3.h \ + src/http/v3/ngx_http_v3_module.h" + ngx_module_srcs="src/http/v3/ngx_http_v3.c \ + src/http/v3/ngx_http_v3_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + + if [ $HTTP_V2_HPACK_ENC = YES ]; then + have=NGX_HTTP_V2_HPACK_ENC . auto/have + fi + if :; then ngx_module_name=ngx_http_static_module ngx_module_incs= @@ -1251,6 +1314,19 @@ . auto/module fi + + +if [ $USE_QUICHE = YES ]; then + ngx_module_type=CORE + ngx_module_name=ngx_quic_module + ngx_module_incs= + ngx_module_deps=src/event/ngx_event_quic.h + ngx_module_srcs=src/event/ngx_event_quic.c + ngx_module_libs= + ngx_module_link=YES + + . auto/module +fi if [ $USE_PCRE = YES ]; then diff -uNr a/auto/options b/auto/options --- a/auto/options 2019-12-24 23:00:09.000000000 +0800 +++ b/auto/options 2020-01-03 10:51:20.000000000 +0800 @@ -58,7 +58,10 @@ HTTP_CHARSET=YES HTTP_GZIP=YES HTTP_SSL=NO +HTTP_SPDY=NO HTTP_V2=NO +HTTP_V2_HPACK_ENC=NO +HTTP_V3=NO HTTP_SSI=YES HTTP_REALIP=NO HTTP_XSLT=NO @@ -147,6 +150,9 @@ USE_OPENSSL=NO OPENSSL=NONE +USE_QUICHE=NO +QUICHE=NONE + USE_ZLIB=NO ZLIB=NONE ZLIB_OPT= @@ -223,7 +229,10 @@ --http-scgi-temp-path=*) NGX_HTTP_SCGI_TEMP_PATH="$value" ;; --with-http_ssl_module) HTTP_SSL=YES ;; + --with-http_spdy_module) HTTP_SPDY=YES ;; --with-http_v2_module) HTTP_V2=YES ;; + --with-http_v2_hpack_enc) HTTP_V2_HPACK_ENC=YES ;; + --with-http_v3_module) HTTP_V3=YES ;; --with-http_realip_module) HTTP_REALIP=YES ;; --with-http_addition_module) HTTP_ADDITION=YES ;; --with-http_xslt_module) HTTP_XSLT=YES ;; @@ -357,6 +366,9 @@ --with-openssl=*) OPENSSL="$value" ;; --with-openssl-opt=*) OPENSSL_OPT="$value" ;; + --with-quiche=*) QUICHE="$value" ;; + --with-quiche-opt=*) QUICHE_OPT="$value" ;; + --with-md5=*) NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG $0: warning: the \"--with-md5\" option is deprecated" @@ -438,7 +450,10 @@ --with-file-aio enable file AIO support --with-http_ssl_module enable ngx_http_ssl_module + --with-http_spdy_module enable ngx_http_spdy_module --with-http_v2_module enable ngx_http_v2_module + --with-http_v2_hpack_enc enable ngx_http_v2_hpack_enc + --with-http_v3_module enable ngx_http_v3_module --with-http_realip_module enable ngx_http_realip_module --with-http_addition_module enable ngx_http_addition_module --with-http_xslt_module enable ngx_http_xslt_module diff -uNr a/src/core/ngx_connection.h b/src/core/ngx_connection.h --- a/src/core/ngx_connection.h 2019-12-24 23:00:09.000000000 +0800 +++ b/src/core/ngx_connection.h 2020-01-03 10:51:59.000000000 +0800 @@ -79,6 +79,9 @@ unsigned deferred_accept:1; unsigned delete_deferred:1; unsigned add_deferred:1; +#if (NGX_QUIC) + unsigned quic:1; +#endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) char *accept_filter; #endif @@ -119,6 +122,7 @@ #define NGX_LOWLEVEL_BUFFERED 0x0f #define NGX_SSL_BUFFERED 0x01 #define NGX_HTTP_V2_BUFFERED 0x02 +#define NGX_SPDY_BUFFERED 0x04 struct ngx_connection_s { @@ -155,6 +159,10 @@ ngx_udp_connection_t *udp; +#if (NGX_QUIC) + ngx_quic_connection_t *quic; +#endif + struct sockaddr *local_sockaddr; socklen_t local_socklen; diff -uNr a/src/core/ngx_core.h b/src/core/ngx_core.h --- a/src/core/ngx_core.h 2019-12-24 23:00:09.000000000 +0800 +++ b/src/core/ngx_core.h 2020-01-02 21:25:20.000000000 +0800 @@ -83,6 +83,9 @@ #if (NGX_OPENSSL) #include #endif +#if (NGX_QUIC) +#include +#endif #include #include #include diff -uNr a/src/core/ngx_murmurhash.c b/src/core/ngx_murmurhash.c --- a/src/core/ngx_murmurhash.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/core/ngx_murmurhash.c 2020-01-02 21:25:20.000000000 +0800 @@ -50,3 +50,63 @@ return h; } + + +uint64_t +ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed) +{ + uint64_t h, k; + + h = seed ^ len; + + while (len >= 8) { + k = data[0]; + k |= data[1] << 8; + k |= data[2] << 16; + k |= data[3] << 24; + k |= (uint64_t)data[4] << 32; + k |= (uint64_t)data[5] << 40; + k |= (uint64_t)data[6] << 48; + k |= (uint64_t)data[7] << 56; + + k *= 0xc6a4a7935bd1e995ull; + k ^= k >> 47; + k *= 0xc6a4a7935bd1e995ull; + + h ^= k; + h *= 0xc6a4a7935bd1e995ull; + + data += 8; + len -= 8; + } + + switch (len) { + case 7: + h ^= (uint64_t)data[6] << 48; + /* fall through */ + case 6: + h ^= (uint64_t)data[5] << 40; + /* fall through */ + case 5: + h ^= (uint64_t)data[4] << 32; + /* fall through */ + case 4: + h ^= data[3] << 24; + /* fall through */ + case 3: + h ^= data[2] << 16; + /* fall through */ + case 2: + h ^= data[1] << 8; + /* fall through */ + case 1: + h ^= data[0]; + h *= 0xc6a4a7935bd1e995ull; + } + + h ^= h >> 47; + h *= 0xc6a4a7935bd1e995ull; + h ^= h >> 47; + + return h; +} diff -uNr a/src/core/ngx_murmurhash.h b/src/core/ngx_murmurhash.h --- a/src/core/ngx_murmurhash.h 2019-12-24 23:00:09.000000000 +0800 +++ b/src/core/ngx_murmurhash.h 2020-01-02 21:25:20.000000000 +0800 @@ -15,5 +15,7 @@ uint32_t ngx_murmur_hash2(u_char *data, size_t len); +uint64_t ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed); + #endif /* _NGX_MURMURHASH_H_INCLUDED_ */ diff -uNr a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/event/ngx_event_openssl.c 2020-01-02 21:25:20.000000000 +0800 @@ -1507,6 +1507,7 @@ sc->buffer = ((flags & NGX_SSL_BUFFER) != 0); sc->buffer_size = ssl->buffer_size; + sc->dyn_rec = ssl->dyn_rec; sc->session_ctx = ssl->ctx; @@ -2400,6 +2401,41 @@ for ( ;; ) { + /* Dynamic record resizing: + We want the initial records to fit into one TCP segment + so we don't get TCP HoL blocking due to TCP Slow Start. + A connection always starts with small records, but after + a given amount of records sent, we make the records larger + to reduce header overhead. + After a connection has idled for a given timeout, begin + the process from the start. The actual parameters are + configurable. If dyn_rec_timeout is 0, we assume dyn_rec is off. */ + + if (c->ssl->dyn_rec.timeout > 0 ) { + + if (ngx_current_msec - c->ssl->dyn_rec_last_write > + c->ssl->dyn_rec.timeout) + { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + c->ssl->dyn_rec_records_sent = 0; + + } else { + if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold * 2) + { + buf->end = buf->start + c->ssl->buffer_size; + + } else if (c->ssl->dyn_rec_records_sent > + c->ssl->dyn_rec.threshold) + { + buf->end = buf->start + c->ssl->dyn_rec.size_hi; + + } else { + buf->end = buf->start + c->ssl->dyn_rec.size_lo; + } + } + } + while (in && buf->last < buf->end && send < limit) { if (in->buf->last_buf || in->buf->flush) { flush = 1; @@ -2507,6 +2543,9 @@ if (n > 0) { + c->ssl->dyn_rec_records_sent++; + c->ssl->dyn_rec_last_write = ngx_current_msec; + if (c->ssl->saved_read_handler) { c->read->handler = c->ssl->saved_read_handler; diff -uNr a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h 2019-12-24 23:00:09.000000000 +0800 +++ b/src/event/ngx_event_openssl.h 2020-01-02 21:25:20.000000000 +0800 @@ -64,10 +64,19 @@ #endif +typedef struct { + ngx_msec_t timeout; + ngx_uint_t threshold; + size_t size_lo; + size_t size_hi; +} ngx_ssl_dyn_rec_t; + + struct ngx_ssl_s { SSL_CTX *ctx; ngx_log_t *log; size_t buffer_size; + ngx_ssl_dyn_rec_t dyn_rec; }; @@ -99,6 +108,10 @@ unsigned in_early:1; unsigned early_preread:1; unsigned write_blocked:1; + + ngx_ssl_dyn_rec_t dyn_rec; + ngx_msec_t dyn_rec_last_write; + ngx_uint_t dyn_rec_records_sent; }; @@ -108,7 +121,7 @@ #define NGX_SSL_DFLT_BUILTIN_SCACHE -5 -#define NGX_SSL_MAX_SESSION_SIZE 4096 +#define NGX_SSL_MAX_SESSION_SIZE 16384 typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t; diff -uNr a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c --- a/src/event/ngx_event_quic.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/event/ngx_event_quic.c 2020-01-02 21:25:20.000000000 +0800 @@ -0,0 +1,581 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + +#include +#include +#include + + +/* Limit outgoing packets to 1200 bytes. This is the minimum value allowed. */ +#define MAX_DATAGRAM_SIZE 1200 + +/* errors */ +#define NGX_QUIC_NO_ERROR 0x0 +#define NGX_QUIC_INTERNAL_ERROR 0x1 + + +static void ngx_quic_read_handler(ngx_event_t *ev); +static void ngx_quic_write_handler(ngx_event_t *ev); + +static void ngx_quic_handshake_completed(ngx_connection_t *c); + +static void ngx_quic_shutdown_handler(ngx_event_t *ev); + +static void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t status); +static void ngx_quic_close_connection(ngx_connection_t *c); + +static ngx_int_t ngx_quic_send_udp_packet(ngx_connection_t *c, uint8_t *buf, + size_t len); + + +static ngx_command_t ngx_quic_commands[] = { + + ngx_null_command +}; + + +static ngx_core_module_t ngx_quic_module_ctx = { + ngx_string("quic"), + NULL, + NULL +}; + + +ngx_module_t ngx_quic_module = { + NGX_MODULE_V1, + &ngx_quic_module_ctx, /* module context */ + ngx_quic_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +ngx_int_t +ngx_quic_create_conf(ngx_quic_t *quic) +{ + quic->config = quiche_config_new(QUICHE_PROTOCOL_VERSION); + if (quic->config == NULL) { + ngx_log_error(NGX_LOG_EMERG, quic->log, 0, "failed to create quic config"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_validate_initial(ngx_event_t *ev, u_char *buf, ssize_t buf_len) +{ + /* Check incoming packet type, if it's not Initial we shouldn't be here. */ + if (((buf[0] & 0x30) >> 4) != 0) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "packet is not quic client initial"); + return NGX_ERROR; + } + + /* Client Initial packets must be at least 1200 bytes. */ + if (buf_len < QUICHE_MIN_CLIENT_INITIAL_LEN) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "quic initial packet is too short"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_create_connection(ngx_quic_t *quic, ngx_connection_t *c) +{ + int rc; + u_char *buf; + size_t buf_len; + quiche_conn *conn; + static uint8_t out[MAX_DATAGRAM_SIZE]; + + uint8_t pkt_type; + uint32_t pkt_version; + + uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; + size_t scid_len = sizeof(scid); + + uint8_t dcid[QUICHE_MAX_CONN_ID_LEN]; + size_t dcid_len = sizeof(dcid); + + uint8_t token[1]; + size_t token_len = sizeof(token); + + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init connection"); + + /* Extract some fields from the client's Initial packet, which was saved + * into c->buffer by ngx_event_recvmsg(). */ + buf = c->buffer->pos; + buf_len = ngx_buf_size(c->buffer); + + rc = quiche_header_info(buf, buf_len, QUICHE_MAX_CONN_ID_LEN, + &pkt_version, &pkt_type, + scid, &scid_len, dcid, &dcid_len, + token, &token_len); + if (rc < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to parse quic header: %d", rc); + return NGX_ERROR; + } + + /* Version mismatch, do version negotiation. */ + if (!quiche_version_is_supported(pkt_version)) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic version negotiation"); + + ssize_t written = quiche_negotiate_version(scid, scid_len, + dcid, dcid_len, + out, sizeof(out)); + + if (written < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to create quic vneg packet: %d", written); + return NGX_ERROR; + } + + if (ngx_quic_send_udp_packet(c, out, written) == NGX_ERROR) { + return NGX_ERROR; + } + + return NGX_DONE; + } + + /* Initialize source connection ID with some random bytes. */ + RAND_bytes(scid, sizeof(scid)); + +#if (NGX_DEBUG) + { + uint8_t dcid_hex[QUICHE_MAX_CONN_ID_LEN * 2], + scid_hex[QUICHE_MAX_CONN_ID_LEN * 2]; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "new quic connection dcid:%*.s new_scid:%*.s", + ngx_hex_dump(dcid_hex, dcid, dcid_len) - dcid_hex, dcid_hex, + ngx_hex_dump(scid_hex, scid, scid_len) - scid_hex, scid_hex); + } +#endif + + conn = quiche_conn_new_with_tls(scid, sizeof(scid), NULL, 0, quic->config, + c->ssl->connection, true); + if (conn == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create quic connection"); + return NGX_ERROR; + } + + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + return NGX_ERROR; + } + + qc->handler = NULL; + + qc->conn = conn; + + c->quic = qc; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handshake(ngx_connection_t *c) +{ + u_char *buf; + size_t buf_len; + ssize_t done; + + c->log->action = "processing QUIC connection"; + + /* Process the client's Initial packet, which was saved into c->buffer by + * ngx_event_recvmsg(). */ + buf = c->buffer->pos; + buf_len = ngx_buf_size(c->buffer); + + done = quiche_conn_recv(c->quic->conn, buf, buf_len); + + if ((done < 0) && (done != QUICHE_ERR_DONE)) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to process quic packet: %d", done); + return NGX_ERROR; + } + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + c->read->handler = ngx_quic_read_handler; + c->write->handler = ngx_quic_write_handler; + + ngx_post_event(c->write, &ngx_posted_events); + + return NGX_AGAIN; +} + + +static void +ngx_quic_read_handler(ngx_event_t *rev) +{ + int n; + static uint8_t buf[65535]; + ngx_connection_t *c; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic read handler"); + + if (rev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection timed out"); + + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + + for (;;) { + n = c->recv(c, buf, sizeof(buf)); + if (n == NGX_AGAIN) { + break; + } + + if (n == NGX_ERROR) { + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + + ssize_t done = quiche_conn_recv(c->quic->conn, buf, n); + + if (done == QUICHE_ERR_DONE) { + break; + } + + if (done < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to process quic packet: %d", done); + + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + } + + if (quiche_conn_is_in_early_data(c->quic->conn) || + quiche_conn_is_established(c->quic->conn)) { + if (!c->ssl->handshaked) { + ngx_quic_handshake_completed(c); + } + + if ((c->quic == NULL) || (c->quic->handler == NULL)) { + return; + } + + /* Notify application layer that there might be stream data to read. */ + c->quic->handler(c); + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic done reading"); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + + ngx_post_event(c->write, &ngx_posted_events); +} + + +static void +ngx_quic_write_handler(ngx_event_t *wev) +{ + ngx_connection_t *c; + ngx_msec_t expiry; + static uint8_t out[MAX_DATAGRAM_SIZE]; + + c = wev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic write handler"); + + if (wev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alarm fired"); + + quiche_conn_on_timeout(c->quic->conn); + } + + if (quiche_conn_is_closed(c->quic->conn)) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection is closed"); + + ngx_quic_finalize_connection(c, NGX_QUIC_NO_ERROR); + return; + } + + for (;;) { + ssize_t written = quiche_conn_send(c->quic->conn, out, sizeof(out)); + + if (written == QUICHE_ERR_DONE) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic done writing"); + break; + } + + if (written < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to create quic packet: %d", written); + + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + + if (ngx_quic_send_udp_packet(c, out, written) == NGX_ERROR) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to send quic packet"); + + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + } + + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR); + return; + } + + expiry = quiche_conn_timeout_as_millis(c->quic->conn); + expiry = ngx_max(expiry, 1); + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + /* quiche_conn_timeout_as_millis() will return UINT64_MAX when the timer + * should be unset (this would be equvalent to returning Option::None in + * Rust). To avoid overflow we need to explicitly check for this value. */ + if (expiry != UINT64_MAX) { + ngx_add_timer(wev, expiry); + } +} + + +static void +ngx_quic_handshake_completed(ngx_connection_t *c) +{ +#if (NGX_DEBUG) + { + char buf[129], *s, *d; +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + const +#endif + SSL_CIPHER *cipher; + + cipher = SSL_get_current_cipher(c->ssl->connection); + + if (cipher) { + SSL_CIPHER_description(cipher, &buf[1], 128); + + for (s = &buf[1], d = buf; *s; s++) { + if (*s == ' ' && *d == ' ') { + continue; + } + + if (*s == LF || *s == CR) { + continue; + } + + *++d = *s; + } + + if (*d != ' ') { + d++; + } + + *d = '\0'; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "QUIC: %s, cipher: \"%s\"", + SSL_get_version(c->ssl->connection), &buf[1]); + + if (SSL_session_reused(c->ssl->connection)) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic reused session"); + } + + } else { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic no shared ciphers"); + } + } +#endif + + ngx_del_timer(c->read); + + c->ssl->handshaked = 1; + + /* Notify application layer that the handshake is complete. */ + c->ssl->handler(c); +} + + +ngx_int_t +ngx_quic_shutdown(ngx_connection_t *c) +{ + if (!quiche_conn_is_closed(c->quic->conn)) { + /* We shouldn't free the connection state yet, as we need to wait for + * the draining timeout to expire. Setup event handlers such that we + * will try again when that happens (or when another event is + * triggered). */ + c->read->handler = ngx_quic_shutdown_handler; + c->write->handler = ngx_quic_shutdown_handler; + + /* We need to flush any remaining frames to the client (including + * CONNECTION_CLOSE), so invoke the write handler. This also takes + * care of setting up the draining timer. */ + ngx_quic_write_handler(c->write); + + /* The QUIC connection might have already been freed inside the write + * handler, in which case we are done. */ + if (c->destroyed) { + return NGX_OK; + } + + return NGX_AGAIN; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "free quic connection"); + + quiche_conn_free(c->quic->conn); + + c->quic = NULL; + c->ssl = NULL; + + return NGX_OK; +} + + +static void +ngx_quic_shutdown_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_connection_handler_pt handler; + + c = ev->data; + handler = c->quic->handler; + + if (ev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alarm fired"); + + quiche_conn_on_timeout(c->quic->conn); + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic shutdown handler"); + + if (ngx_quic_shutdown(c) == NGX_AGAIN) { + return; + } + + handler(c); +} + + +static void +ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t status) +{ + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "finalize quic connection: %d", c->fd); + + c->error = 1; + + quiche_conn_close(c->quic->conn, false, status, NULL, 0); + + /* Notify the application layer that the connection is in an error + * state and will be closed. */ + if (c->quic->handler != NULL) { + c->quic->handler(c); + return; + } + + ngx_quic_close_connection(c); +} + + +static void +ngx_quic_close_connection(ngx_connection_t *c) +{ + ngx_pool_t *pool; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "close quic connection: %d", c->fd); + + if (c->quic) { + if (ngx_quic_shutdown(c) == NGX_AGAIN) { + c->quic->handler = ngx_quic_close_connection; + return; + } + + if (c->destroyed) { + return; + } + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} + + +void +ngx_quic_cleanup_ctx(void *data) +{ + ngx_quic_t *quic = data; + + quiche_config_free(quic->config); +} + + +static ngx_int_t +ngx_quic_send_udp_packet(ngx_connection_t *c, uint8_t *buf, size_t len) +{ + ngx_buf_t out_buf = {0}; + ngx_chain_t out_chain = {0}; + + /* The send_chain() API takes an ngx_chain_t parameter instead of a simple + * buffer, so we need to initialize the chain such that it contains only a + * single buffer. + * + * The c->send_chain() call is required (instead of just c->send()) because + * it uses the sendmsg(2) syscall (instead of sendto(2)), which allows us to + * specify the correct source IP address for the connection. */ + + out_buf.start = out_buf.pos = buf; + out_buf.end = out_buf.last = buf + len; + out_buf.memory = 1; + out_buf.flush = 1; + + out_chain.buf = &out_buf; + out_chain.next = NULL; + + if (c->send_chain(c, &out_chain, 0) == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + return NGX_OK; +} diff -uNr a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h --- a/src/event/ngx_event_quic.h 1970-01-01 08:00:00.000000000 +0800 +++ b/src/event/ngx_event_quic.h 2020-01-02 21:25:20.000000000 +0800 @@ -0,0 +1,49 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_H_INCLUDED_ +#define _NGX_EVENT_QUIC_H_INCLUDED_ + + +#include + +#include +#include + +#include + +typedef struct ngx_quic_s ngx_quic_t; +typedef struct ngx_quic_connection_s ngx_quic_connection_t; + +struct ngx_quic_s { + quiche_config *config; + ngx_log_t *log; +}; + +struct ngx_quic_connection_s { + quiche_conn *conn; + + ngx_connection_handler_pt handler; +}; + + +ngx_int_t ngx_quic_create_conf(ngx_quic_t *quic); + +ngx_int_t ngx_quic_validate_initial(ngx_event_t *ev, u_char *buf, + ssize_t buf_len); + +ngx_int_t ngx_quic_create_connection(ngx_quic_t *quic, ngx_connection_t *c); + +ngx_int_t ngx_quic_create_ssl_connection(ngx_ssl_t *ssl, ngx_connection_t *c, + ngx_uint_t flags); + +ngx_int_t ngx_quic_handshake(ngx_connection_t *c); + +ngx_int_t ngx_quic_shutdown(ngx_connection_t *c); + +void ngx_quic_cleanup_ctx(void *data); + +#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff -uNr a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c --- a/src/event/ngx_event_udp.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/event/ngx_event_udp.c 2020-01-02 21:25:20.000000000 +0800 @@ -276,6 +276,14 @@ (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1); #endif +#if (NGX_QUIC) + if (ls->quic) { + if (ngx_quic_validate_initial(ev, buffer, n) != NGX_OK) { + goto next; + } + } +#endif + ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n; diff -uNr a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.c 2020-01-03 10:59:08.000000000 +0800 @@ -249,6 +249,41 @@ offsetof(ngx_http_ssl_srv_conf_t, early_data), NULL }, + { ngx_string("ssl_dyn_rec_enable"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_enable), + NULL }, + + { ngx_string("ssl_dyn_rec_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_timeout), + NULL }, + + { ngx_string("ssl_dyn_rec_size_lo"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_lo), + NULL }, + + { ngx_string("ssl_dyn_rec_size_hi"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_hi), + NULL }, + + { ngx_string("ssl_dyn_rec_threshold"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_threshold), + NULL }, + ngx_null_command }; @@ -371,10 +406,10 @@ #if (NGX_DEBUG) unsigned int i; #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_HTTP_V3) ngx_http_connection_t *hc; #endif -#if (NGX_HTTP_V2 || NGX_DEBUG) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_DEBUG) ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); @@ -388,9 +423,20 @@ } #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_HTTP_V3) hc = c->data; +#endif +#if (NGX_HTTP_V2 && NGX_HTTP_SPDY) + if (hc->addr_conf->http2 && hc->addr_conf->spdy) { + srv = (unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE + NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; + srvlen = sizeof(NGX_HTTP_V2_ALPN_ADVERTISE NGX_SPDY_NPN_ADVERTISE + NGX_HTTP_NPN_ADVERTISE) - 1; + + } else +#endif +#if (NGX_HTTP_V2) if (hc->addr_conf->http2) { srv = (unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; @@ -398,6 +444,20 @@ } else #endif +#if (NGX_HTTP_SPDY) + if (hc->addr_conf->spdy) { + srv = (unsigned char *) NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; + srvlen = sizeof(NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1; + + } else +#endif +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + srv = (unsigned char *) QUICHE_H3_APPLICATION_PROTOCOL; + srvlen = sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1; + + } else +#endif { srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE; srvlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1; @@ -425,19 +485,32 @@ ngx_http_ssl_npn_advertised(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, unsigned int *outlen, void *arg) { -#if (NGX_HTTP_V2 || NGX_DEBUG) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_DEBUG) ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "SSL NPN advertised"); #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY) { ngx_http_connection_t *hc; hc = c->data; +#endif + +#if (NGX_HTTP_V2 && NGX_HTTP_SPDY) + if (hc->addr_conf->http2 && hc->addr_conf->spdy) { + *out = (unsigned char *) NGX_HTTP_V2_NPN_ADVERTISE + NGX_SPDY_NPN_ADVERTISE + NGX_HTTP_NPN_ADVERTISE; + *outlen = sizeof(NGX_HTTP_V2_NPN_ADVERTISE NGX_SPDY_NPN_ADVERTISE + NGX_HTTP_NPN_ADVERTISE) - 1; + return SSL_TLSEXT_ERR_OK; + } else +#endif +#if (NGX_HTTP_V2) if (hc->addr_conf->http2) { *out = (unsigned char *) NGX_HTTP_V2_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; @@ -445,6 +518,20 @@ return SSL_TLSEXT_ERR_OK; } +#endif +#if (NGX_HTTP_V2 && NGX_HTTP_SPDY) + else +#endif +#if (NGX_HTTP_SPDY) + if (hc->addr_conf->spdy) { + *out = (unsigned char *) NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; + *outlen = sizeof(NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1; + + return SSL_TLSEXT_ERR_OK; + } +#endif + +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY) } #endif @@ -580,6 +667,11 @@ sscf->session_ticket_keys = NGX_CONF_UNSET_PTR; sscf->stapling = NGX_CONF_UNSET; sscf->stapling_verify = NGX_CONF_UNSET; + sscf->dyn_rec_enable = NGX_CONF_UNSET; + sscf->dyn_rec_timeout = NGX_CONF_UNSET_MSEC; + sscf->dyn_rec_size_lo = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_size_hi = NGX_CONF_UNSET_SIZE; + sscf->dyn_rec_threshold = NGX_CONF_UNSET_UINT; return sscf; } @@ -647,6 +739,20 @@ ngx_conf_merge_str_value(conf->stapling_responder, prev->stapling_responder, ""); + ngx_conf_merge_value(conf->dyn_rec_enable, prev->dyn_rec_enable, 0); + ngx_conf_merge_msec_value(conf->dyn_rec_timeout, prev->dyn_rec_timeout, + 1000); + /* Default sizes for the dynamic record sizes are defined to fit maximal + TLS + IPv6 overhead in a single TCP segment for lo and 3 segments for hi: + 1369 = 1500 - 40 (IP) - 20 (TCP) - 10 (Time) - 61 (Max TLS overhead) */ + ngx_conf_merge_size_value(conf->dyn_rec_size_lo, prev->dyn_rec_size_lo, + 1369); + /* 4229 = (1500 - 40 - 20 - 10) * 3 - 61 */ + ngx_conf_merge_size_value(conf->dyn_rec_size_hi, prev->dyn_rec_size_hi, + 4229); + ngx_conf_merge_uint_value(conf->dyn_rec_threshold, prev->dyn_rec_threshold, + 40); + conf->ssl.log = cf->log; if (conf->enable) { @@ -857,6 +963,28 @@ return NGX_CONF_ERROR; } + if (conf->dyn_rec_enable) { + conf->ssl.dyn_rec.timeout = conf->dyn_rec_timeout; + conf->ssl.dyn_rec.threshold = conf->dyn_rec_threshold; + + if (conf->buffer_size > conf->dyn_rec_size_lo) { + conf->ssl.dyn_rec.size_lo = conf->dyn_rec_size_lo; + + } else { + conf->ssl.dyn_rec.size_lo = conf->buffer_size; + } + + if (conf->buffer_size > conf->dyn_rec_size_hi) { + conf->ssl.dyn_rec.size_hi = conf->dyn_rec_size_hi; + + } else { + conf->ssl.dyn_rec.size_hi = conf->buffer_size; + } + + } else { + conf->ssl.dyn_rec.timeout = 0; + } + return NGX_CONF_OK; } diff -uNr a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/modules/ngx_http_ssl_module.h 2020-01-02 21:25:20.000000000 +0800 @@ -61,6 +61,12 @@ u_char *file; ngx_uint_t line; + + ngx_flag_t dyn_rec_enable; + ngx_msec_t dyn_rec_timeout; + size_t dyn_rec_size_lo; + size_t dyn_rec_size_hi; + ngx_uint_t dyn_rec_threshold; } ngx_http_ssl_srv_conf_t; diff -uNr a/src/http/ngx_http.c b/src/http/ngx_http.c --- a/src/http/ngx_http.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/ngx_http.c 2020-01-03 11:38:04.000000000 +0800 @@ -1141,6 +1141,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, ngx_http_listen_opt_t *lsopt) { + int t; in_port_t p; ngx_uint_t i; struct sockaddr *sa; @@ -1159,11 +1160,13 @@ sa = lsopt->sockaddr; p = ngx_inet_get_port(sa); + t = lsopt->quic ? SOCK_DGRAM : SOCK_STREAM; port = cmcf->ports->elts; for (i = 0; i < cmcf->ports->nelts; i++) { - if (p != port[i].port || sa->sa_family != port[i].family) { + if (p != port[i].port || sa->sa_family != port[i].family + || t != port[i].type) { continue; } @@ -1182,6 +1185,7 @@ port->family = sa->sa_family; port->port = p; port->addrs.elts = NULL; + port->type = t; return ngx_http_add_address(cf, cscf, port, lsopt); } @@ -1199,6 +1203,12 @@ #if (NGX_HTTP_V2) ngx_uint_t http2; #endif +#if (NGX_HTTP_SPDY) + ngx_uint_t spdy; +#endif +#if (NGX_HTTP_V3) + ngx_uint_t quic; +#endif /* * we cannot compare whole sockaddr struct's as kernel @@ -1234,6 +1244,12 @@ #if (NGX_HTTP_V2) http2 = lsopt->http2 || addr[i].opt.http2; #endif +#if (NGX_HTTP_SPDY) + spdy = lsopt->spdy || addr[i].opt.spdy; +#endif +#if (NGX_HTTP_V3) + quic = lsopt->quic || addr[i].opt.quic; +#endif if (lsopt->set) { @@ -1270,6 +1286,12 @@ #if (NGX_HTTP_V2) addr[i].opt.http2 = http2; #endif +#if (NGX_HTTP_SPDY) + addr[i].opt.spdy = spdy; +#endif +#if (NGX_HTTP_V3) + addr[i].opt.quic = quic; +#endif return NGX_OK; } @@ -1313,6 +1335,18 @@ #endif +#if (NGX_HTTP_SPDY && NGX_HTTP_SSL \ + && !defined TLSEXT_TYPE_application_layer_protocol_negotiation \ + && !defined TLSEXT_TYPE_next_proto_neg) + if (lsopt->spdy && lsopt->ssl) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "nginx was built with OpenSSL that lacks ALPN " + "and NPN support, SPDY is not enabled for %s", + lsopt->addr); + } + +#endif + addr = ngx_array_push(&port->addrs); if (addr == NULL) { return NGX_ERROR; @@ -1688,6 +1722,12 @@ break; } +#if (NGX_HTTP_V3) + if (addr[i].opt.quic) { + ls->type = SOCK_DGRAM; + } +#endif + addr++; last--; } @@ -1770,6 +1810,12 @@ ls->reuseport = addr->opt.reuseport; #endif +#if (NGX_HTTP_V3) + ls->quic = addr->opt.quic; + + ls->wildcard = addr->opt.wildcard; +#endif + return ls; } @@ -1802,7 +1848,13 @@ #if (NGX_HTTP_V2) addrs[i].conf.http2 = addr[i].opt.http2; #endif +#if (NGX_HTTP_SPDY) + addrs[i].conf.spdy = addr[i].opt.spdy; +#endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; +#if (NGX_HTTP_V3) + addrs[i].conf.quic = addr[i].opt.quic; +#endif if (addr[i].hash.buckets == NULL && (addr[i].wc_head == NULL @@ -1868,6 +1920,9 @@ addrs6[i].conf.http2 = addr[i].opt.http2; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; +#if (NGX_HTTP_V3) + addrs6[i].conf.quic = addr[i].opt.quic; +#endif if (addr[i].hash.buckets == NULL && (addr[i].wc_head == NULL diff -uNr a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/ngx_http_core_module.c 2020-01-03 11:42:40.000000000 +0800 @@ -1939,6 +1939,13 @@ return NGX_DECLINED; } +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + r->gzip_ok = 1; + return NGX_OK; + } +#endif + ae = r->headers_in.accept_encoding; if (ae == NULL) { return NGX_DECLINED; @@ -2296,6 +2303,9 @@ #if (NGX_HTTP_V2) sr->stream = r->stream; #endif +#if (NGX_HTTP_SPDY) + sr->spdy_stream = r->spdy_stream; +#endif sr->method = NGX_HTTP_GET; sr->http_version = r->http_version; @@ -4001,11 +4011,15 @@ } if (ngx_strcmp(value[n].data, "spdy") == 0) { - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "invalid parameter \"spdy\": " - "ngx_http_spdy_module was superseded " - "by ngx_http_v2_module"); +#if (NGX_HTTP_SPDY) + lsopt.spdy = 1; continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"spdy\" parameter requires " + "ngx_http_spdy_module"); + return NGX_CONF_ERROR; +#endif } if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) { @@ -4104,6 +4118,13 @@ continue; } +#if (NGX_HTTP_V3) + if (ngx_strcmp(value[n].data, "quic") == 0) { + lsopt.quic = 1; + continue; + } +#endif + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[n]); return NGX_CONF_ERROR; diff -uNr a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h --- a/src/http/ngx_http_core_module.h 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/ngx_http_core_module.h 2020-01-03 11:43:43.000000000 +0800 @@ -75,6 +75,9 @@ unsigned wildcard:1; unsigned ssl:1; unsigned http2:1; +#if (NGX_HTTP_SPDY) + unsigned spdy:1; +#endif #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif @@ -82,6 +85,7 @@ unsigned reuseport:1; unsigned so_keepalive:2; unsigned proxy_protocol:1; + unsigned quic:1; int backlog; int rcvbuf; @@ -237,7 +241,11 @@ unsigned ssl:1; unsigned http2:1; +#if (NGX_HTTP_SPDY) + unsigned spdy:1; +#endif unsigned proxy_protocol:1; + unsigned quic:1; }; @@ -268,6 +276,7 @@ ngx_int_t family; in_port_t port; ngx_array_t addrs; /* array of ngx_http_conf_addr_t */ + ngx_int_t type; } ngx_http_conf_port_t; diff -uNr a/src/http/ngx_http.h b/src/http/ngx_http.h --- a/src/http/ngx_http.h 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/ngx_http.h 2020-01-03 11:51:41.000000000 +0800 @@ -18,8 +18,12 @@ typedef struct ngx_http_cache_s ngx_http_cache_t; typedef struct ngx_http_file_cache_s ngx_http_file_cache_t; typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t; +#if (NGX_HTTP_SPDY) +typedef struct ngx_http_spdy_stream_s ngx_http_spdy_stream_t; +#endif typedef struct ngx_http_chunked_s ngx_http_chunked_t; typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t; +typedef struct ngx_http_v3_stream_s ngx_http_v3_stream_t; typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); @@ -38,6 +42,12 @@ #if (NGX_HTTP_V2) #include #endif +#if (NGX_HTTP_SPDY) +#include +#endif +#if (NGX_HTTP_V3) +#include +#endif #if (NGX_HTTP_CACHE) #include #endif diff -uNr a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c --- a/src/http/ngx_http_request_body.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/ngx_http_request_body.c 2020-01-03 11:53:46.000000000 +0800 @@ -84,6 +84,18 @@ goto done; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + rc = ngx_http_spdy_read_request_body(r, post_handler); + goto done; + } +#endif +#if (NGX_HTTP_V3) + if (r->qstream) { + rc = ngx_http_v3_read_request_body(r); + goto done; + } +#endif preread = r->header_in->last - r->header_in->pos; @@ -226,6 +238,18 @@ } #endif +#if (NGX_HTTP_V3) + if (r->qstream) { + rc = ngx_http_v3_read_unbuffered_request_body(r); + + if (rc == NGX_OK) { + r->reading_body = 0; + } + + return rc; + } +#endif + if (r->connection->read->timedout) { r->connection->timedout = 1; return NGX_HTTP_REQUEST_TIME_OUT; @@ -524,6 +548,18 @@ return NGX_OK; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + r->spdy_stream->skip_data = 1; + return NGX_OK; + } +#endif +#if (NGX_HTTP_V3) + if (r->qstream) { + r->qstream->skip_data = 1; + return NGX_OK; + } +#endif if (ngx_http_test_expect(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -809,6 +845,9 @@ #if (NGX_HTTP_V2) || r->stream != NULL #endif +#if (NGX_HTTP_V3) + || r->qstream != NULL +#endif ) { return NGX_OK; diff -uNr a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/ngx_http_request.c 2020-01-03 11:50:40.000000000 +0800 @@ -64,6 +64,10 @@ static void ngx_http_ssl_handshake_handler(ngx_connection_t *c); #endif +#if (NGX_HTTP_V3) +static void ngx_http_quic_handshake(ngx_event_t *rev); +#endif + static char *ngx_http_client_errors[] = { @@ -324,6 +328,11 @@ rev->handler = ngx_http_wait_request_handler; c->write->handler = ngx_http_empty_handler; +#if (NGX_HTTP_SPDY) + if (hc->addr_conf->spdy) { + rev->handler = ngx_http_spdy_init; + } +#endif #if (NGX_HTTP_V2) if (hc->addr_conf->http2) { rev->handler = ngx_http_v2_init; @@ -349,6 +358,18 @@ c->log->action = "reading PROXY protocol"; } +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + hc->quic = 1; + + /* We already have a UDP packet in the connection buffer, so we don't + * need to wait for another read event to kick-off the handshake. */ + ngx_add_timer(rev, c->listening->post_accept_timeout); + ngx_http_quic_handshake(rev); + return; + } +#endif + if (rev->ready) { /* the deferred accept(), iocp */ @@ -797,7 +818,7 @@ c->ssl->no_wait_shutdown = 1; -#if (NGX_HTTP_V2 \ +#if ((NGX_HTTP_V2 || NGX_HTTP_V3) \ && (defined TLSEXT_TYPE_application_layer_protocol_negotiation \ || defined TLSEXT_TYPE_next_proto_neg)) { @@ -807,7 +828,7 @@ hc = c->data; - if (hc->addr_conf->http2) { + if (hc->addr_conf->http2 || hc->addr_conf->quic) { #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation SSL_get0_alpn_selected(c->ssl->connection, &data, &len); @@ -822,11 +843,57 @@ SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len); #endif + } + +#if (NGX_HTTP_V2) + if (hc->addr_conf->http2) { if (len == 2 && data[0] == 'h' && data[1] == '2') { ngx_http_v2_init(c->read); return; } } +#endif + +#if (NGX_HTTP_SPDY \ + && (defined TLSEXT_TYPE_application_layer_protocol_negotiation \ + || defined TLSEXT_TYPE_next_proto_neg)) + { + unsigned int len; + const unsigned char *data; + static const ngx_str_t spdy = ngx_string(NGX_SPDY_NPN_NEGOTIATED); + +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + +#ifdef TLSEXT_TYPE_next_proto_neg + if (len == 0) { + SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len); + } +#endif + +#else /* TLSEXT_TYPE_next_proto_neg */ + SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len); +#endif + + if (len == spdy.len && ngx_strncmp(data, spdy.data, spdy.len) == 0) { + ngx_http_spdy_init(c->read); + return; + } + } +#endif + +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + if (len >= 2 && data[0] == 'h' && data[1] == '3') { + ngx_http_v3_init(c->read); + return; + } + + ngx_http_close_connection(c); + return; + } +#endif + } #endif @@ -1033,6 +1100,68 @@ #endif +#if (NGX_HTTP_V3) + +static void +ngx_http_quic_handshake(ngx_event_t *rev) +{ + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_connection_t *hc; + ngx_http_v3_srv_conf_t *qscf; + ngx_http_ssl_srv_conf_t *sscf; + + c = rev->data; + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "http check quic handshake"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_http_close_connection(c); + return; + } + + if (c->close) { + ngx_http_close_connection(c); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "https quic handshake"); + + sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, + ngx_http_ssl_module); + + if (ngx_ssl_create_connection(&sscf->ssl, c, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + qscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + + if (ngx_quic_create_connection(&qscf->quic, c) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + rc = ngx_quic_handshake(c); + + if (rc == NGX_AGAIN) { + + if (!rev->timer_set) { + ngx_add_timer(rev, c->listening->post_accept_timeout); + } + + c->ssl->handler = ngx_http_ssl_handshake_handler; + return; + } + + ngx_http_ssl_handshake_handler(c); +} + +#endif + static void ngx_http_process_request_line(ngx_event_t *rev) @@ -2687,6 +2816,20 @@ } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + ngx_http_close_request(r, 0); + return; + } +#endif + +#if (NGX_HTTP_V3) + if (r->qstream) { + ngx_http_close_request(r, 0); + return; + } +#endif + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->main->count != 1) { @@ -2895,6 +3038,31 @@ } #endif +#if (NGX_HTTP_SPDY) + + if (r->spdy_stream) { + if (c->error) { + err = 0; + goto closed; + } + + return; + } + +#endif + +#if (NGX_HTTP_V3) + + if (r->qstream) { + if (c->error) { + err = 0; + goto closed; + } + + return; + } + +#endif #if (NGX_HAVE_KQUEUE) @@ -3563,7 +3731,22 @@ } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + ngx_http_spdy_close_stream(r->spdy_stream, rc); + return; + } +#endif + +#if (NGX_HTTP_V3) + if (r->qstream) { + ngx_http_v3_close_stream(r->qstream, rc); + return; + } +#endif + ngx_http_free_request(r, rc); + ngx_http_close_connection(c); } @@ -3684,6 +3867,17 @@ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "close http connection: %d", c->fd); +#if (NGX_HTTP_V3) + + if (c->quic) { + if (ngx_quic_shutdown(c) == NGX_AGAIN) { + c->quic->handler = ngx_http_close_connection; + return; + } + } + +#endif + #if (NGX_HTTP_SSL) if (c->ssl) { diff -uNr a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/ngx_http_request.h 2020-01-03 11:52:35.000000000 +0800 @@ -24,6 +24,7 @@ #define NGX_HTTP_VERSION_10 1000 #define NGX_HTTP_VERSION_11 1001 #define NGX_HTTP_VERSION_20 2000 +#define NGX_HTTP_VERSION_3 3000 #define NGX_HTTP_UNKNOWN 0x0001 #define NGX_HTTP_GET 0x0002 @@ -323,6 +324,7 @@ ngx_chain_t *free; unsigned ssl:1; + unsigned quic:1; unsigned proxy_protocol:1; } ngx_http_connection_t; @@ -432,6 +434,9 @@ int *captures; u_char *captures_data; #endif +#if (NGX_HTTP_SPDY) + ngx_http_spdy_stream_t *spdy_stream; +#endif size_t limit_rate; size_t limit_rate_after; @@ -445,6 +450,7 @@ ngx_http_connection_t *http_connection; ngx_http_v2_stream_t *stream; + ngx_http_v3_stream_t *qstream; ngx_http_log_handler_pt log_handler; diff -uNr a/src/http/ngx_http_spdy.c b/src/http/ngx_http_spdy.c --- a/src/http/ngx_http_spdy.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/ngx_http_spdy.c 2020-01-03 11:55:53.000000000 +0800 @@ -0,0 +1,3701 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include + +#include + + +#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED) + +#define ngx_str5cmp(m, c0, c1, c2, c3, c4) \ + *(uint32_t *) m == (c3 << 24 | c2 << 16 | c1 << 8 | c0) \ + && m[4] == c4 + +#else + +#define ngx_str5cmp(m, c0, c1, c2, c3, c4) \ + m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 && m[4] == c4 + +#endif + + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_spdy_frame_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_spdy_frame_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#else + +#define ngx_spdy_frame_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_spdy_frame_parse_uint32(p) \ + ((p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#endif + +#define ngx_spdy_frame_parse_sid(p) \ + (ngx_spdy_frame_parse_uint32(p) & 0x7fffffff) +#define ngx_spdy_frame_parse_delta(p) \ + (ngx_spdy_frame_parse_uint32(p) & 0x7fffffff) + + +#define ngx_spdy_ctl_frame_check(h) \ + (((h) & 0xffff0000) == ngx_spdy_ctl_frame_head(0)) +#define ngx_spdy_data_frame_check(h) \ + (!((h) & (uint32_t) NGX_SPDY_CTL_BIT << 31)) + +#define ngx_spdy_ctl_frame_type(h) ((h) & 0x0000ffff) +#define ngx_spdy_frame_flags(p) ((p) >> 24) +#define ngx_spdy_frame_length(p) ((p) & 0x00ffffff) +#define ngx_spdy_frame_id(p) ((p) & 0x00ffffff) + + +#define NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE 4096 +#define NGX_SPDY_CTL_FRAME_BUFFER_SIZE 16 + +#define NGX_SPDY_PROTOCOL_ERROR 1 +#define NGX_SPDY_INVALID_STREAM 2 +#define NGX_SPDY_REFUSED_STREAM 3 +#define NGX_SPDY_UNSUPPORTED_VERSION 4 +#define NGX_SPDY_CANCEL 5 +#define NGX_SPDY_INTERNAL_ERROR 6 +#define NGX_SPDY_FLOW_CONTROL_ERROR 7 +#define NGX_SPDY_STREAM_IN_USE 8 +#define NGX_SPDY_STREAM_ALREADY_CLOSED 9 +/* deprecated 10 */ +#define NGX_SPDY_FRAME_TOO_LARGE 11 + +#define NGX_SPDY_SETTINGS_MAX_STREAMS 4 +#define NGX_SPDY_SETTINGS_INIT_WINDOW 7 + +#define NGX_SPDY_SETTINGS_FLAG_PERSIST 0x01 +#define NGX_SPDY_SETTINGS_FLAG_PERSISTED 0x02 + +#define NGX_SPDY_MAX_WINDOW NGX_MAX_INT32_VALUE +#define NGX_SPDY_CONNECTION_WINDOW 65536 +#define NGX_SPDY_INIT_STREAM_WINDOW 65536 +#define NGX_SPDY_STREAM_WINDOW NGX_SPDY_MAX_WINDOW + +typedef struct { + ngx_uint_t hash; + u_char len; + u_char header[7]; + ngx_int_t (*handler)(ngx_http_request_t *r); +} ngx_http_spdy_request_header_t; + + +static void ngx_http_spdy_read_handler(ngx_event_t *rev); +static void ngx_http_spdy_write_handler(ngx_event_t *wev); +static void ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc); + +static u_char *ngx_http_spdy_proxy_protocol(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_head(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_syn_stream(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_headers(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_headers_skip(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_headers_error(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_window_update(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_data(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_read_data(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_rst_stream(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_ping(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_skip(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_settings(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_complete(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_save(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end, ngx_http_spdy_handler_pt handler); + +static u_char *ngx_http_spdy_state_inflate_error( + ngx_http_spdy_connection_t *sc, int rc); +static u_char *ngx_http_spdy_state_protocol_error( + ngx_http_spdy_connection_t *sc); +static u_char *ngx_http_spdy_state_internal_error( + ngx_http_spdy_connection_t *sc); + +static ngx_int_t ngx_http_spdy_send_window_update( + ngx_http_spdy_connection_t *sc, ngx_uint_t sid, ngx_uint_t delta); +static ngx_int_t ngx_http_spdy_send_rst_stream(ngx_http_spdy_connection_t *sc, + ngx_uint_t sid, ngx_uint_t status, ngx_uint_t priority); +static ngx_int_t ngx_http_spdy_send_settings(ngx_http_spdy_connection_t *sc); +static ngx_int_t ngx_http_spdy_settings_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); +static ngx_http_spdy_out_frame_t *ngx_http_spdy_get_ctl_frame( + ngx_http_spdy_connection_t *sc, size_t size, ngx_uint_t priority); +static ngx_int_t ngx_http_spdy_ctl_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); + +static ngx_http_spdy_stream_t *ngx_http_spdy_create_stream( + ngx_http_spdy_connection_t *sc, ngx_uint_t id, ngx_uint_t priority); +static ngx_http_spdy_stream_t *ngx_http_spdy_get_stream_by_id( + ngx_http_spdy_connection_t *sc, ngx_uint_t sid); +#define ngx_http_spdy_streams_index_size(sscf) (sscf->streams_index_mask + 1) +#define ngx_http_spdy_stream_index(sscf, sid) \ + ((sid >> 1) & sscf->streams_index_mask) + +static ngx_int_t ngx_http_spdy_parse_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_alloc_large_header_buffer(ngx_http_request_t *r); + +static ngx_int_t ngx_http_spdy_handle_request_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_method(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_scheme(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_host(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_path(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_version(ngx_http_request_t *r); + +static ngx_int_t ngx_http_spdy_construct_request_line(ngx_http_request_t *r); +static void ngx_http_spdy_run_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_init_request_body(ngx_http_request_t *r); + +static ngx_int_t ngx_http_spdy_terminate_stream(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream, ngx_uint_t status); + +static void ngx_http_spdy_close_stream_handler(ngx_event_t *ev); + +static void ngx_http_spdy_handle_connection_handler(ngx_event_t *rev); +static void ngx_http_spdy_keepalive_handler(ngx_event_t *rev); +static void ngx_http_spdy_finalize_connection(ngx_http_spdy_connection_t *sc, + ngx_int_t rc); + +static ngx_int_t ngx_http_spdy_adjust_windows(ngx_http_spdy_connection_t *sc, + ssize_t delta); + +static void ngx_http_spdy_pool_cleanup(void *data); + +static void *ngx_http_spdy_zalloc(void *opaque, u_int items, u_int size); +static void ngx_http_spdy_zfree(void *opaque, void *address); + + +static const u_char ngx_http_spdy_dict[] = { + 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, /* - - - - o p t i */ + 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, /* o n s - - - - h */ + 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, /* e a d - - - - p */ + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, /* o s t - - - - p */ + 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, /* u t - - - - d e */ + 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, /* l e t e - - - - */ + 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, /* t r a c e - - - */ + 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, /* - a c c e p t - */ + 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, /* - - - a c c e p */ + 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, /* t - c h a r s e */ + 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, /* t - - - - a c c */ + 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, /* e p t - e n c o */ + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, /* d i n g - - - - */ + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, /* a c c e p t - l */ + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, /* a n g u a g e - */ + 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, /* - - - a c c e p */ + 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, /* t - r a n g e s */ + 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, /* - - - - a g e - */ + 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, /* - - - a l l o w */ + 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, /* - - - - a u t h */ + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, /* o r i z a t i o */ + 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, /* n - - - - c a c */ + 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, /* h e - c o n t r */ + 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, /* o l - - - - c o */ + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, /* n n e c t i o n */ + 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */ + 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, /* e n t - b a s e */ + 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */ + 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, /* e n t - e n c o */ + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, /* d i n g - - - - */ + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, /* c o n t e n t - */ + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, /* l a n g u a g e */ + 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */ + 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, /* e n t - l e n g */ + 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, /* t h - - - - c o */ + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, /* n t e n t - l o */ + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, /* c a t i o n - - */ + 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, /* - - c o n t e n */ + 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, /* t - m d 5 - - - */ + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, /* - c o n t e n t */ + 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, /* - r a n g e - - */ + 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, /* - - c o n t e n */ + 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, /* t - t y p e - - */ + 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, /* - - d a t e - - */ + 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, /* - - e t a g - - */ + 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, /* - - e x p e c t */ + 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, /* - - - - e x p i */ + 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, /* r e s - - - - f */ + 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, /* r o m - - - - h */ + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, /* o s t - - - - i */ + 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, /* f - m a t c h - */ + 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, /* - - - i f - m o */ + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, /* d i f i e d - s */ + 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, /* i n c e - - - - */ + 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, /* i f - n o n e - */ + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, /* m a t c h - - - */ + 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, /* - i f - r a n g */ + 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, /* e - - - - i f - */ + 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, /* u n m o d i f i */ + 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, /* e d - s i n c e */ + 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, /* - - - - l a s t */ + 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, /* - m o d i f i e */ + 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, /* d - - - - l o c */ + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, /* a t i o n - - - */ + 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, /* - m a x - f o r */ + 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, /* w a r d s - - - */ + 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, /* - p r a g m a - */ + 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, /* - - - p r o x y */ + 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, /* - a u t h e n t */ + 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, /* i c a t e - - - */ + 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, /* - p r o x y - a */ + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, /* u t h o r i z a */ + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, /* t i o n - - - - */ + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, /* r a n g e - - - */ + 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, /* - r e f e r e r */ + 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, /* - - - - r e t r */ + 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, /* y - a f t e r - */ + 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, /* - - - s e r v e */ + 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, /* r - - - - t e - */ + 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, /* - - - t r a i l */ + 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, /* e r - - - - t r */ + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, /* a n s f e r - e */ + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, /* n c o d i n g - */ + 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, /* - - - u p g r a */ + 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, /* d e - - - - u s */ + 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, /* e r - a g e n t */ + 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, /* - - - - v a r y */ + 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, /* - - - - v i a - */ + 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, /* - - - w a r n i */ + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, /* n g - - - - w w */ + 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, /* w - a u t h e n */ + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, /* t i c a t e - - */ + 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, /* - - m e t h o d */ + 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, /* - - - - g e t - */ + 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, /* - - - s t a t u */ + 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, /* s - - - - 2 0 0 */ + 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, /* - O K - - - - v */ + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, /* e r s i o n - - */ + 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, /* - - H T T P - 1 */ + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, /* - 1 - - - - u r */ + 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, /* l - - - - p u b */ + 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, /* l i c - - - - s */ + 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, /* e t - c o o k i */ + 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, /* e - - - - k e e */ + 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, /* p - a l i v e - */ + 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, /* - - - o r i g i */ + 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, /* n 1 0 0 1 0 1 2 */ + 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, /* 0 1 2 0 2 2 0 5 */ + 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, /* 2 0 6 3 0 0 3 0 */ + 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, /* 2 3 0 3 3 0 4 3 */ + 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, /* 0 5 3 0 6 3 0 7 */ + 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, /* 4 0 2 4 0 5 4 0 */ + 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, /* 6 4 0 7 4 0 8 4 */ + 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, /* 0 9 4 1 0 4 1 1 */ + 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, /* 4 1 2 4 1 3 4 1 */ + 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, /* 4 4 1 5 4 1 6 4 */ + 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, /* 1 7 5 0 2 5 0 4 */ + 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, /* 5 0 5 2 0 3 - N */ + 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, /* o n - A u t h o */ + 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, /* r i t a t i v e */ + 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, /* - I n f o r m a */ + 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, /* t i o n 2 0 4 - */ + 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, /* N o - C o n t e */ + 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, /* n t 3 0 1 - M o */ + 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, /* v e d - P e r m */ + 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, /* a n e n t l y 4 */ + 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, /* 0 0 - B a d - R */ + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, /* e q u e s t 4 0 */ + 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, /* 1 - U n a u t h */ + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, /* o r i z e d 4 0 */ + 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, /* 3 - F o r b i d */ + 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, /* d e n 4 0 4 - N */ + 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, /* o t - F o u n d */ + 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, /* 5 0 0 - I n t e */ + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, /* r n a l - S e r */ + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, /* v e r - E r r o */ + 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, /* r 5 0 1 - N o t */ + 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, /* - I m p l e m e */ + 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, /* n t e d 5 0 3 - */ + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, /* S e r v i c e - */ + 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, /* U n a v a i l a */ + 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, /* b l e J a n - F */ + 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, /* e b - M a r - A */ + 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, /* p r - M a y - J */ + 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, /* u n - J u l - A */ + 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, /* u g - S e p t - */ + 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, /* O c t - N o v - */ + 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, /* D e c - 0 0 - 0 */ + 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, /* 0 - 0 0 - M o n */ + 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, /* - - T u e - - W */ + 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, /* e d - - T h u - */ + 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, /* - F r i - - S a */ + 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, /* t - - S u n - - */ + 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, /* G M T c h u n k */ + 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, /* e d - t e x t - */ + 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, /* h t m l - i m a */ + 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, /* g e - p n g - i */ + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, /* m a g e - j p g */ + 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, /* - i m a g e - g */ + 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, /* i f - a p p l i */ + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, /* c a t i o n - x */ + 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, /* m l - a p p l i */ + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, /* c a t i o n - x */ + 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, /* h t m l - x m l */ + 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, /* - t e x t - p l */ + 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, /* a i n - t e x t */ + 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, /* - j a v a s c r */ + 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, /* i p t - p u b l */ + 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, /* i c p r i v a t */ + 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, /* e m a x - a g e */ + 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, /* - g z i p - d e */ + 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, /* f l a t e - s d */ + 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, /* c h c h a r s e */ + 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, /* t - u t f - 8 c */ + 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, /* h a r s e t - i */ + 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, /* s o - 8 8 5 9 - */ + 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, /* 1 - u t f - - - */ + 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e /* - e n q - 0 - */ +}; + + +static ngx_http_spdy_request_header_t ngx_http_spdy_request_headers[] = { + { 0, 6, "method", ngx_http_spdy_parse_method }, + { 0, 6, "scheme", ngx_http_spdy_parse_scheme }, + { 0, 4, "host", ngx_http_spdy_parse_host }, + { 0, 4, "path", ngx_http_spdy_parse_path }, + { 0, 7, "version", ngx_http_spdy_parse_version }, +}; + +#define NGX_SPDY_REQUEST_HEADERS \ + (sizeof(ngx_http_spdy_request_headers) \ + / sizeof(ngx_http_spdy_request_header_t)) + + +void +ngx_http_spdy_init(ngx_event_t *rev) +{ + int rc; + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_main_conf_t *smcf; + ngx_http_spdy_connection_t *sc; + + c = rev->data; + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "init spdy request"); + + c->log->action = "processing SPDY"; + + smcf = ngx_http_get_module_main_conf(hc->conf_ctx, ngx_http_spdy_module); + + if (smcf->recv_buffer == NULL) { + smcf->recv_buffer = ngx_palloc(ngx_cycle->pool, smcf->recv_buffer_size); + if (smcf->recv_buffer == NULL) { + ngx_http_close_connection(c); + return; + } + } + + sc = ngx_pcalloc(c->pool, sizeof(ngx_http_spdy_connection_t)); + if (sc == NULL) { + ngx_http_close_connection(c); + return; + } + + sc->connection = c; + sc->http_connection = hc; + + sc->send_window = NGX_SPDY_CONNECTION_WINDOW; + sc->recv_window = NGX_SPDY_CONNECTION_WINDOW; + + sc->init_window = NGX_SPDY_INIT_STREAM_WINDOW; + + sc->handler = hc->proxy_protocol ? ngx_http_spdy_proxy_protocol + : ngx_http_spdy_state_head; + + sc->zstream_in.zalloc = ngx_http_spdy_zalloc; + sc->zstream_in.zfree = ngx_http_spdy_zfree; + sc->zstream_in.opaque = sc; + + rc = inflateInit(&sc->zstream_in); + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "inflateInit() failed: %d", rc); + ngx_http_close_connection(c); + return; + } + + sc->zstream_out.zalloc = ngx_http_spdy_zalloc; + sc->zstream_out.zfree = ngx_http_spdy_zfree; + sc->zstream_out.opaque = sc; + + sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_spdy_module); + + rc = deflateInit2(&sc->zstream_out, (int) sscf->headers_comp, + Z_DEFLATED, 11, 4, Z_DEFAULT_STRATEGY); + + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "deflateInit2() failed: %d", rc); + ngx_http_close_connection(c); + return; + } + + rc = deflateSetDictionary(&sc->zstream_out, ngx_http_spdy_dict, + sizeof(ngx_http_spdy_dict)); + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "deflateSetDictionary() failed: %d", rc); + ngx_http_close_connection(c); + return; + } + + sc->pool = ngx_create_pool(sscf->pool_size, sc->connection->log); + if (sc->pool == NULL) { + ngx_http_close_connection(c); + return; + } + + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_pool_cleanup_file_t)); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_spdy_pool_cleanup; + cln->data = sc; + + sc->streams_index = ngx_pcalloc(sc->pool, + ngx_http_spdy_streams_index_size(sscf) + * sizeof(ngx_http_spdy_stream_t *)); + if (sc->streams_index == NULL) { + ngx_http_close_connection(c); + return; + } + + if (ngx_http_spdy_send_settings(sc) == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (ngx_http_spdy_send_window_update(sc, 0, NGX_SPDY_MAX_WINDOW + - sc->recv_window) + == NGX_ERROR) + { + ngx_http_close_connection(c); + return; + } + + sc->recv_window = NGX_SPDY_MAX_WINDOW; + + ngx_queue_init(&sc->waiting); + ngx_queue_init(&sc->posted); + + c->data = sc; + + rev->handler = ngx_http_spdy_read_handler; + c->write->handler = ngx_http_spdy_write_handler; + + ngx_http_spdy_read_handler(rev); +} + + +static void +ngx_http_spdy_read_handler(ngx_event_t *rev) +{ + u_char *p, *end; + size_t available; + ssize_t n; + ngx_connection_t *c; + ngx_http_spdy_main_conf_t *smcf; + ngx_http_spdy_connection_t *sc; + + c = rev->data; + sc = c->data; + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy read handler"); + + sc->blocked = 1; + + smcf = ngx_http_get_module_main_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + available = smcf->recv_buffer_size - 2 * NGX_SPDY_STATE_BUFFER_SIZE; + + do { + p = smcf->recv_buffer; + + ngx_memcpy(p, sc->buffer, NGX_SPDY_STATE_BUFFER_SIZE); + end = p + sc->buffer_used; + + n = c->recv(c, end, available); + + if (n == NGX_AGAIN) { + break; + } + + if (n == 0 && (sc->incomplete || sc->processing)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client prematurely closed connection"); + } + + if (n == 0 || n == NGX_ERROR) { + ngx_http_spdy_finalize_connection(sc, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + end += n; + + sc->buffer_used = 0; + sc->incomplete = 0; + + do { + p = sc->handler(sc, p, end); + + if (p == NULL) { + return; + } + + } while (p != end); + + } while (rev->ready); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (sc->last_out && ngx_http_spdy_send_output_queue(sc) == NGX_ERROR) { + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + sc->blocked = 0; + + if (sc->processing) { + if (rev->timer_set) { + ngx_del_timer(rev); + } + return; + } + + ngx_http_spdy_handle_connection(sc); +} + + +static void +ngx_http_spdy_write_handler(ngx_event_t *wev) +{ + ngx_int_t rc; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_connection_t *sc; + + c = wev->data; + sc = c->data; + + if (wev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy write event timed out"); + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy write handler"); + + sc->blocked = 1; + + rc = ngx_http_spdy_send_output_queue(sc); + + if (rc == NGX_ERROR) { + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + while (!ngx_queue_empty(&sc->posted)) { + q = ngx_queue_head(&sc->posted); + + ngx_queue_remove(q); + + stream = ngx_queue_data(q, ngx_http_spdy_stream_t, queue); + + stream->handled = 0; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "run spdy stream %ui", stream->id); + + wev = stream->request->connection->write; + wev->handler(wev); + } + + sc->blocked = 0; + + if (rc == NGX_AGAIN) { + return; + } + + ngx_http_spdy_handle_connection(sc); +} + + +ngx_int_t +ngx_http_spdy_send_output_queue(ngx_http_spdy_connection_t *sc) +{ + int tcp_nodelay; + ngx_chain_t *cl; + ngx_event_t *wev; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + ngx_http_spdy_out_frame_t *out, *frame, *fn; + + c = sc->connection; + + if (c->error) { + return NGX_ERROR; + } + + wev = c->write; + + if (!wev->ready) { + return NGX_OK; + } + + cl = NULL; + out = NULL; + + for (frame = sc->last_out; frame; frame = fn) { + frame->last->next = cl; + cl = frame->first; + + fn = frame->next; + frame->next = out; + out = frame; + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy frame out: %p sid:%ui prio:%ui bl:%d len:%uz", + out, out->stream ? out->stream->id : 0, out->priority, + out->blocked, out->length); + } + + cl = c->send_chain(c, cl, 0); + + if (cl == NGX_CHAIN_ERROR) { + goto error; + } + + clcf = ngx_http_get_module_loc_conf(sc->http_connection->conf_ctx, + ngx_http_core_module); + + if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { + goto error; + } + + if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { + if (ngx_tcp_push(c->fd) == -1) { + ngx_connection_error(c, ngx_socket_errno, ngx_tcp_push_n " failed"); + goto error; + } + + c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; + tcp_nodelay = ngx_tcp_nodelay_and_tcp_nopush ? 1 : 0; + + } else { + tcp_nodelay = 1; + } + + if (tcp_nodelay + && clcf->tcp_nodelay + && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) + { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) + == -1) + { +#if (NGX_SOLARIS) + /* Solaris returns EINVAL if a socket has been shut down */ + c->log_error = NGX_ERROR_IGNORE_EINVAL; +#endif + + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + + c->log_error = NGX_ERROR_INFO; + goto error; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + if (cl) { + ngx_add_timer(wev, clcf->send_timeout); + + } else { + if (wev->timer_set) { + ngx_del_timer(wev); + } + } + + for ( /* void */ ; out; out = fn) { + fn = out->next; + + if (out->handler(sc, out) != NGX_OK) { + out->blocked = 1; + out->priority = NGX_SPDY_HIGHEST_PRIORITY; + break; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy frame sent: %p sid:%ui bl:%d len:%uz", + out, out->stream ? out->stream->id : 0, + out->blocked, out->length); + } + + frame = NULL; + + for ( /* void */ ; out; out = fn) { + fn = out->next; + out->next = frame; + frame = out; + } + + sc->last_out = frame; + + return NGX_OK; + +error: + + c->error = 1; + + if (!sc->blocked) { + ngx_post_event(wev, &ngx_posted_events); + } + + return NGX_ERROR; +} + + +static void +ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc) +{ + ngx_connection_t *c; + ngx_http_spdy_srv_conf_t *sscf; + + if (sc->last_out || sc->processing) { + return; + } + + c = sc->connection; + + if (c->error) { + ngx_http_close_connection(c); + return; + } + + if (c->buffered) { + return; + } + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + if (sc->incomplete) { + ngx_add_timer(c->read, sscf->recv_timeout); + return; + } + + if (ngx_terminate || ngx_exiting) { + ngx_http_close_connection(c); + return; + } + + ngx_destroy_pool(sc->pool); + + sc->pool = NULL; + sc->free_ctl_frames = NULL; + sc->free_fake_connections = NULL; + +#if (NGX_HTTP_SSL) + if (c->ssl) { + ngx_ssl_free_buffer(c); + } +#endif + + c->destroyed = 1; + c->idle = 1; + ngx_reusable_connection(c, 1); + + c->write->handler = ngx_http_empty_handler; + c->read->handler = ngx_http_spdy_keepalive_handler; + + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + ngx_add_timer(c->read, sscf->keepalive_timeout); +} + + +static u_char * +ngx_http_spdy_proxy_protocol(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_log_t *log; + + log = sc->connection->log; + log->action = "reading PROXY protocol"; + + pos = ngx_proxy_protocol_read(sc->connection, pos, end); + + log->action = "processing SPDY"; + + if (pos == NULL) { + return ngx_http_spdy_state_protocol_error(sc); + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_head(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + uint32_t head, flen; + ngx_uint_t type; + + if (end - pos < NGX_SPDY_FRAME_HEADER_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_head); + } + + head = ngx_spdy_frame_parse_uint32(pos); + + pos += sizeof(uint32_t); + + flen = ngx_spdy_frame_parse_uint32(pos); + + sc->flags = ngx_spdy_frame_flags(flen); + sc->length = ngx_spdy_frame_length(flen); + + pos += sizeof(uint32_t); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "process spdy frame head:%08XD f:%Xd l:%uz", + head, sc->flags, sc->length); + + if (ngx_spdy_ctl_frame_check(head)) { + type = ngx_spdy_ctl_frame_type(head); + + switch (type) { + + case NGX_SPDY_SYN_STREAM: + return ngx_http_spdy_state_syn_stream(sc, pos, end); + + case NGX_SPDY_SYN_REPLY: + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent unexpected SYN_REPLY frame"); + return ngx_http_spdy_state_protocol_error(sc); + + case NGX_SPDY_RST_STREAM: + return ngx_http_spdy_state_rst_stream(sc, pos, end); + + case NGX_SPDY_SETTINGS: + return ngx_http_spdy_state_settings(sc, pos, end); + + case NGX_SPDY_PING: + return ngx_http_spdy_state_ping(sc, pos, end); + + case NGX_SPDY_GOAWAY: + return ngx_http_spdy_state_skip(sc, pos, end); /* TODO */ + + case NGX_SPDY_HEADERS: + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent unexpected HEADERS frame"); + return ngx_http_spdy_state_protocol_error(sc); + + case NGX_SPDY_WINDOW_UPDATE: + return ngx_http_spdy_state_window_update(sc, pos, end); + + default: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy control frame with unknown type %ui", type); + return ngx_http_spdy_state_skip(sc, pos, end); + } + } + + if (ngx_spdy_data_frame_check(head)) { + sc->stream = ngx_http_spdy_get_stream_by_id(sc, head); + return ngx_http_spdy_state_data(sc, pos, end); + } + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent invalid frame"); + + return ngx_http_spdy_state_protocol_error(sc); +} + + +static u_char * +ngx_http_spdy_state_syn_stream(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_uint_t sid, prio; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_srv_conf_t *sscf; + + if (end - pos < NGX_SPDY_SYN_STREAM_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_syn_stream); + } + + if (sc->length <= NGX_SPDY_SYN_STREAM_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame with incorrect length %uz", + sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sc->length -= NGX_SPDY_SYN_STREAM_SIZE; + + sid = ngx_spdy_frame_parse_sid(pos); + prio = pos[8] >> 5; + + pos += NGX_SPDY_SYN_STREAM_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy SYN_STREAM frame sid:%ui prio:%ui", sid, prio); + + if (sid % 2 == 0 || sid <= sc->last_sid) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame " + "with invalid Stream-ID %ui", sid); + + stream = ngx_http_spdy_get_stream_by_id(sc, sid); + + if (stream) { + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_PROTOCOL_ERROR) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + } + + return ngx_http_spdy_state_protocol_error(sc); + } + + sc->last_sid = sid; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + if (sc->processing >= sscf->concurrent_streams) { + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "concurrent streams exceeded %ui", sc->processing); + + if (ngx_http_spdy_send_rst_stream(sc, sid, NGX_SPDY_REFUSED_STREAM, + prio) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + stream = ngx_http_spdy_create_stream(sc, sid, prio); + if (stream == NULL) { + return ngx_http_spdy_state_internal_error(sc); + } + + stream->in_closed = (sc->flags & NGX_SPDY_FLAG_FIN) ? 1 : 0; + + stream->request->request_length = NGX_SPDY_FRAME_HEADER_SIZE + + NGX_SPDY_SYN_STREAM_SIZE + + sc->length; + + sc->stream = stream; + + return ngx_http_spdy_state_headers(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_headers(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + int z; + size_t size; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_http_request_t *r; + + size = end - pos; + + if (size == 0) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + } + + if (size > sc->length) { + size = sc->length; + } + + r = sc->stream->request; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "process spdy header block %uz of %uz", size, sc->length); + + buf = r->header_in; + + sc->zstream_in.next_in = pos; + sc->zstream_in.avail_in = size; + sc->zstream_in.next_out = buf->last; + + /* one byte is reserved for null-termination of the last header value */ + sc->zstream_in.avail_out = buf->end - buf->last - 1; + + z = inflate(&sc->zstream_in, Z_NO_FLUSH); + + if (z == Z_NEED_DICT) { + z = inflateSetDictionary(&sc->zstream_in, ngx_http_spdy_dict, + sizeof(ngx_http_spdy_dict)); + + if (z != Z_OK) { + if (z == Z_DATA_ERROR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent SYN_STREAM frame with header " + "block encoded using wrong dictionary: %ul", + (u_long) sc->zstream_in.adler); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "inflateSetDictionary() failed: %d", z); + + return ngx_http_spdy_state_internal_error(sc); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy inflateSetDictionary(): %d", z); + + z = sc->zstream_in.avail_in ? inflate(&sc->zstream_in, Z_NO_FLUSH) + : Z_OK; + } + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_in.next_in, sc->zstream_in.next_out, + sc->zstream_in.avail_in, sc->zstream_in.avail_out, + z); + + if (z != Z_OK) { + return ngx_http_spdy_state_inflate_error(sc, z); + } + + sc->length -= sc->zstream_in.next_in - pos; + pos = sc->zstream_in.next_in; + + buf->last = sc->zstream_in.next_out; + + if (r->headers_in.headers.part.elts == NULL) { + + if (buf->last - buf->pos < NGX_SPDY_NV_NUM_SIZE) { + + if (sc->length == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "premature end of spdy header block"); + + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + } + + sc->entries = ngx_spdy_frame_parse_uint32(buf->pos); + + buf->pos += NGX_SPDY_NV_NUM_SIZE; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy header block has %ui entries", + sc->entries); + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + if (ngx_array_init(&r->headers_in.cookies, r->pool, 2, + sizeof(ngx_table_elt_t *)) + != NGX_OK) + { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + } + + while (sc->entries) { + + rc = ngx_http_spdy_parse_header(r); + + switch (rc) { + + case NGX_DONE: + sc->entries--; + + case NGX_OK: + break; + + case NGX_AGAIN: + + if (sc->zstream_in.avail_in) { + + rc = ngx_http_spdy_alloc_large_header_buffer(r); + + if (rc == NGX_DECLINED) { + ngx_http_finalize_request(r, + NGX_HTTP_REQUEST_HEADER_TOO_LARGE); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + if (rc != NGX_OK) { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + /* null-terminate the last processed header name or value */ + *buf->pos = '\0'; + + buf = r->header_in; + + sc->zstream_in.next_out = buf->last; + + /* one byte is reserved for null-termination */ + sc->zstream_in.avail_out = buf->end - buf->last - 1; + + z = inflate(&sc->zstream_in, Z_NO_FLUSH); + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_in.next_in, sc->zstream_in.next_out, + sc->zstream_in.avail_in, sc->zstream_in.avail_out, + z); + + if (z != Z_OK) { + return ngx_http_spdy_state_inflate_error(sc, z); + } + + sc->length -= sc->zstream_in.next_in - pos; + pos = sc->zstream_in.next_in; + + buf->last = sc->zstream_in.next_out; + + continue; + } + + if (sc->length == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "premature end of spdy header block"); + + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + + case NGX_HTTP_PARSE_INVALID_HEADER: + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + + default: /* NGX_ERROR */ + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + /* a header line has been parsed successfully */ + + rc = ngx_http_spdy_handle_request_header(r); + + if (rc != NGX_OK) { + if (rc == NGX_HTTP_PARSE_INVALID_HEADER) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + if (rc != NGX_ABORT) { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + } + + if (buf->pos != buf->last || sc->zstream_in.avail_in) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "incorrect number of spdy header block entries"); + + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + if (sc->length) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + } + + /* null-terminate the last header value */ + *buf->pos = '\0'; + + ngx_http_spdy_run_request(r); + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_headers_skip(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + int n; + size_t size; + u_char buffer[NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE]; + + if (sc->length == 0) { + return ngx_http_spdy_state_complete(sc, pos, end); + } + + size = end - pos; + + if (size == 0) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers_skip); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy header block skip %uz of %uz", size, sc->length); + + sc->zstream_in.next_in = pos; + sc->zstream_in.avail_in = (size < sc->length) ? size : sc->length; + + while (sc->zstream_in.avail_in) { + sc->zstream_in.next_out = buffer; + sc->zstream_in.avail_out = NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE; + + n = inflate(&sc->zstream_in, Z_NO_FLUSH); + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_in.next_in, sc->zstream_in.next_out, + sc->zstream_in.avail_in, sc->zstream_in.avail_out, + n); + + if (n != Z_OK) { + return ngx_http_spdy_state_inflate_error(sc, n); + } + } + + pos = sc->zstream_in.next_in; + + if (size < sc->length) { + sc->length -= size; + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers_skip); + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_headers_error(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_http_spdy_stream_t *stream; + + stream = sc->stream; + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame for stream %ui " + "with invalid header block", stream->id); + + if (ngx_http_spdy_send_rst_stream(sc, stream->id, NGX_SPDY_PROTOCOL_ERROR, + stream->priority) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + + stream->out_closed = 1; + + ngx_http_spdy_close_stream(stream, NGX_HTTP_BAD_REQUEST); + + return ngx_http_spdy_state_headers_skip(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_window_update(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + size_t delta; + ngx_uint_t sid; + ngx_event_t *wev; + ngx_queue_t *q; + ngx_http_spdy_stream_t *stream; + + if (end - pos < NGX_SPDY_WINDOW_UPDATE_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_window_update); + } + + if (sc->length != NGX_SPDY_WINDOW_UPDATE_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent WINDOW_UPDATE frame " + "with incorrect length %uz", sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sid = ngx_spdy_frame_parse_sid(pos); + + pos += NGX_SPDY_SID_SIZE; + + delta = ngx_spdy_frame_parse_delta(pos); + + pos += NGX_SPDY_DELTA_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy WINDOW_UPDATE sid:%ui delta:%uz", sid, delta); + + if (sid) { + stream = ngx_http_spdy_get_stream_by_id(sc, sid); + + if (stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "unknown spdy stream"); + + return ngx_http_spdy_state_complete(sc, pos, end); + } + + if (stream->send_window > (ssize_t) (NGX_SPDY_MAX_WINDOW - delta)) { + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated flow control for stream %ui: " + "received WINDOW_UPDATE frame with delta %uz " + "not allowed for window %z", + sid, delta, stream->send_window); + + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_FLOW_CONTROL_ERROR) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_complete(sc, pos, end); + } + + stream->send_window += delta; + + if (stream->exhausted) { + stream->exhausted = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + } + } + + } else { + sc->send_window += delta; + + if (sc->send_window > NGX_SPDY_MAX_WINDOW) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated connection flow control: " + "received WINDOW_UPDATE frame with delta %uz " + "not allowed for window %uz", + delta, sc->send_window); + + return ngx_http_spdy_state_protocol_error(sc); + } + + while (!ngx_queue_empty(&sc->waiting)) { + q = ngx_queue_head(&sc->waiting); + + ngx_queue_remove(q); + + stream = ngx_queue_data(q, ngx_http_spdy_stream_t, queue); + + stream->handled = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + + if (sc->send_window == 0) { + break; + } + } + } + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_data(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_http_spdy_stream_t *stream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy DATA frame"); + + if (sc->length > sc->recv_window) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated connection flow control: " + "received DATA frame length %uz, available window %uz", + sc->length, sc->recv_window); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sc->recv_window -= sc->length; + + if (sc->recv_window < NGX_SPDY_MAX_WINDOW / 4) { + + if (ngx_http_spdy_send_window_update(sc, 0, + NGX_SPDY_MAX_WINDOW + - sc->recv_window) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + sc->recv_window = NGX_SPDY_MAX_WINDOW; + } + + stream = sc->stream; + + if (stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "unknown spdy stream"); + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + if (sc->length > stream->recv_window) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated flow control for stream %ui: " + "received DATA frame length %uz, available window %uz", + stream->id, sc->length, stream->recv_window); + + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_FLOW_CONTROL_ERROR) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + stream->recv_window -= sc->length; + + if (stream->recv_window < NGX_SPDY_STREAM_WINDOW / 4) { + + if (ngx_http_spdy_send_window_update(sc, stream->id, + NGX_SPDY_STREAM_WINDOW + - stream->recv_window) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + stream->recv_window = NGX_SPDY_STREAM_WINDOW; + } + + if (stream->in_closed) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent DATA frame for half-closed stream %ui", + stream->id); + + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_STREAM_ALREADY_CLOSED) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + return ngx_http_spdy_state_read_data(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_read_data(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + size_t size; + ssize_t n; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_temp_file_t *tf; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + stream = sc->stream; + + if (stream == NULL) { + return ngx_http_spdy_state_skip(sc, pos, end); + } + + if (stream->skip_data) { + + if (sc->flags & NGX_SPDY_FLAG_FIN) { + stream->in_closed = 1; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "skipping spdy DATA frame, reason: %d", + stream->skip_data); + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + size = end - pos; + + if (size > sc->length) { + size = sc->length; + } + + r = stream->request; + + if (r->request_body == NULL + && ngx_http_spdy_init_request_body(r) != NGX_OK) + { + stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR; + return ngx_http_spdy_state_skip(sc, pos, end); + } + + rb = r->request_body; + tf = rb->temp_file; + buf = rb->buf; + + if (size) { + rb->rest += size; + + if (r->headers_in.content_length_n != -1 + && r->headers_in.content_length_n < rb->rest) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client intended to send body data " + "larger than declared"); + + stream->skip_data = NGX_SPDY_DATA_ERROR; + goto error; + + } else { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->client_max_body_size + && clcf->client_max_body_size < rb->rest) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send " + "too large chunked body: %O bytes", rb->rest); + + stream->skip_data = NGX_SPDY_DATA_ERROR; + goto error; + } + } + + sc->length -= size; + + if (tf) { + buf->start = pos; + buf->pos = pos; + + pos += size; + + buf->end = pos; + buf->last = pos; + + n = ngx_write_chain_to_temp_file(tf, rb->bufs); + + /* TODO: n == 0 or not complete and level event */ + + if (n == NGX_ERROR) { + stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR; + goto error; + } + + tf->offset += n; + + } else { + buf->last = ngx_cpymem(buf->last, pos, size); + pos += size; + } + + r->request_length += size; + } + + if (sc->length) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_read_data); + } + + if (sc->flags & NGX_SPDY_FLAG_FIN) { + + stream->in_closed = 1; + + if (r->headers_in.content_length_n < 0) { + r->headers_in.content_length_n = rb->rest; + + } else if (r->headers_in.content_length_n != rb->rest) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream: " + "only %O out of %O bytes of request body received", + rb->rest, r->headers_in.content_length_n); + + stream->skip_data = NGX_SPDY_DATA_ERROR; + goto error; + } + + if (tf) { + ngx_memzero(buf, sizeof(ngx_buf_t)); + + buf->in_file = 1; + buf->file_last = tf->file.offset; + buf->file = &tf->file; + + rb->buf = NULL; + } + + if (rb->post_handler) { + r->read_event_handler = ngx_http_block_reading; + rb->post_handler(r); + } + } + + return ngx_http_spdy_state_complete(sc, pos, end); + +error: + + if (rb->post_handler) { + + if (stream->skip_data == NGX_SPDY_DATA_ERROR) { + rc = (r->headers_in.content_length_n == -1) + ? NGX_HTTP_REQUEST_ENTITY_TOO_LARGE + : NGX_HTTP_BAD_REQUEST; + + } else { + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_http_finalize_request(r, rc); + } + + return ngx_http_spdy_state_skip(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_rst_stream(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_uint_t sid, status; + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_spdy_stream_t *stream; + + if (end - pos < NGX_SPDY_RST_STREAM_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_rst_stream); + } + + if (sc->length != NGX_SPDY_RST_STREAM_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent RST_STREAM frame with incorrect length %uz", + sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sid = ngx_spdy_frame_parse_sid(pos); + + pos += NGX_SPDY_SID_SIZE; + + status = ngx_spdy_frame_parse_uint32(pos); + + pos += sizeof(uint32_t); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy RST_STREAM sid:%ui st:%ui", sid, status); + + stream = ngx_http_spdy_get_stream_by_id(sc, sid); + + if (stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "unknown spdy stream"); + + return ngx_http_spdy_state_complete(sc, pos, end); + } + + stream->in_closed = 1; + stream->out_closed = 1; + + fc = stream->request->connection; + fc->error = 1; + + switch (status) { + + case NGX_SPDY_CANCEL: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client canceled stream %ui", sid); + break; + + case NGX_SPDY_INTERNAL_ERROR: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client terminated stream %ui due to internal error", + sid); + break; + + default: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client terminated stream %ui with status %ui", + sid, status); + break; + } + + ev = fc->read; + ev->handler(ev); + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_ping(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + if (end - pos < NGX_SPDY_PING_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_ping); + } + + if (sc->length != NGX_SPDY_PING_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent PING frame with incorrect length %uz", + sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy PING frame"); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_PING_SIZE, + NGX_SPDY_HIGHEST_PRIORITY); + if (frame == NULL) { + return ngx_http_spdy_state_internal_error(sc); + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_PING); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_PING_SIZE); + + p = ngx_cpymem(p, pos, NGX_SPDY_PING_SIZE); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + pos += NGX_SPDY_PING_SIZE; + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_skip(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + size_t size; + + size = end - pos; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy frame skip %uz of %uz", size, sc->length); + + if (size < sc->length) { + sc->length -= size; + return ngx_http_spdy_state_save(sc, end, end, + ngx_http_spdy_state_skip); + } + + return ngx_http_spdy_state_complete(sc, pos + sc->length, end); +} + + +static u_char * +ngx_http_spdy_state_settings(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_uint_t fid, val; + + if (sc->entries == 0) { + + if (end - pos < NGX_SPDY_SETTINGS_NUM_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_settings); + } + + sc->entries = ngx_spdy_frame_parse_uint32(pos); + + pos += NGX_SPDY_SETTINGS_NUM_SIZE; + sc->length -= NGX_SPDY_SETTINGS_NUM_SIZE; + + if (sc->length < sc->entries * NGX_SPDY_SETTINGS_PAIR_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SETTINGS frame with incorrect " + "length %uz or number of entries %ui", + sc->length, sc->entries); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy SETTINGS frame has %ui entries", sc->entries); + } + + while (sc->entries) { + if (end - pos < NGX_SPDY_SETTINGS_PAIR_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_settings); + } + + sc->entries--; + sc->length -= NGX_SPDY_SETTINGS_PAIR_SIZE; + + fid = ngx_spdy_frame_parse_uint32(pos); + + pos += NGX_SPDY_SETTINGS_FID_SIZE; + + val = ngx_spdy_frame_parse_uint32(pos); + + pos += NGX_SPDY_SETTINGS_VAL_SIZE; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy SETTINGS entry fl:%ui id:%ui val:%ui", + ngx_spdy_frame_flags(fid), ngx_spdy_frame_id(fid), val); + + if (ngx_spdy_frame_flags(fid) == NGX_SPDY_SETTINGS_FLAG_PERSISTED) { + continue; + } + + switch (ngx_spdy_frame_id(fid)) { + + case NGX_SPDY_SETTINGS_INIT_WINDOW: + + if (val > NGX_SPDY_MAX_WINDOW) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SETTINGS frame with " + "incorrect INIT_WINDOW value: %ui", val); + + return ngx_http_spdy_state_protocol_error(sc); + } + + if (ngx_http_spdy_adjust_windows(sc, val - sc->init_window) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + + sc->init_window = val; + + continue; + } + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_complete(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy frame complete pos:%p end:%p", pos, end); + + if (pos > end) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "receive buffer overrun"); + + ngx_debug_point(); + return ngx_http_spdy_state_internal_error(sc); + } + + sc->handler = ngx_http_spdy_state_head; + sc->stream = NULL; + + return pos; +} + + +static u_char * +ngx_http_spdy_state_save(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end, ngx_http_spdy_handler_pt handler) +{ + size_t size; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy frame state save pos:%p end:%p handler:%p", + pos, end, handler); + + size = end - pos; + + if (size > NGX_SPDY_STATE_BUFFER_SIZE) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "state buffer overflow: %uz bytes required", size); + + ngx_debug_point(); + return ngx_http_spdy_state_internal_error(sc); + } + + ngx_memcpy(sc->buffer, pos, NGX_SPDY_STATE_BUFFER_SIZE); + + sc->buffer_used = size; + sc->handler = handler; + sc->incomplete = 1; + + return end; +} + + +static u_char * +ngx_http_spdy_state_inflate_error(ngx_http_spdy_connection_t *sc, int rc) +{ + if (rc == Z_DATA_ERROR || rc == Z_STREAM_END) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame with " + "corrupted header block, inflate() failed: %d", rc); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_error(NGX_LOG_ERR, sc->connection->log, 0, + "inflate() failed: %d", rc); + + return ngx_http_spdy_state_internal_error(sc); +} + + +static u_char * +ngx_http_spdy_state_protocol_error(ngx_http_spdy_connection_t *sc) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy state protocol error"); + + if (sc->stream) { + sc->stream->out_closed = 1; + ngx_http_spdy_close_stream(sc->stream, NGX_HTTP_BAD_REQUEST); + } + + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + + return NULL; +} + + +static u_char * +ngx_http_spdy_state_internal_error(ngx_http_spdy_connection_t *sc) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy state internal error"); + + if (sc->stream) { + sc->stream->out_closed = 1; + ngx_http_spdy_close_stream(sc->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_INTERNAL_SERVER_ERROR); + + return NULL; +} + + +static ngx_int_t +ngx_http_spdy_send_window_update(ngx_http_spdy_connection_t *sc, ngx_uint_t sid, + ngx_uint_t delta) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send WINDOW_UPDATE sid:%ui delta:%ui", sid, delta); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_WINDOW_UPDATE_SIZE, + NGX_SPDY_HIGHEST_PRIORITY); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_WINDOW_UPDATE); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_WINDOW_UPDATE_SIZE); + + p = ngx_spdy_frame_write_sid(p, sid); + p = ngx_spdy_frame_aligned_write_uint32(p, delta); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_send_rst_stream(ngx_http_spdy_connection_t *sc, ngx_uint_t sid, + ngx_uint_t status, ngx_uint_t priority) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + if (sc->connection->error) { + return NGX_OK; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send RST_STREAM sid:%ui st:%ui", sid, status); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_RST_STREAM_SIZE, + priority); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_RST_STREAM); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_RST_STREAM_SIZE); + + p = ngx_spdy_frame_write_sid(p, sid); + p = ngx_spdy_frame_aligned_write_uint32(p, status); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} + + +#if 0 +static ngx_int_t +ngx_http_spdy_send_goaway(ngx_http_spdy_connection_t *sc) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send GOAWAY sid:%ui", sc->last_sid); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_GOAWAY_SIZE, + NGX_SPDY_HIGHEST_PRIORITY); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_GOAWAY); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_GOAWAY_SIZE); + + p = ngx_spdy_frame_write_sid(p, sc->last_sid); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} +#endif + + +static ngx_int_t +ngx_http_spdy_send_settings(ngx_http_spdy_connection_t *sc) +{ + u_char *p; + ngx_buf_t *buf; + ngx_chain_t *cl; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_out_frame_t *frame; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send SETTINGS frame"); + + frame = ngx_palloc(sc->pool, sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NGX_ERROR; + } + + cl = ngx_alloc_chain_link(sc->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + buf = ngx_create_temp_buf(sc->pool, NGX_SPDY_FRAME_HEADER_SIZE + + NGX_SPDY_SETTINGS_NUM_SIZE + + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE); + if (buf == NULL) { + return NGX_ERROR; + } + + buf->last_buf = 1; + + cl->buf = buf; + cl->next = NULL; + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_spdy_settings_frame_handler; + frame->stream = NULL; +#if (NGX_DEBUG) + frame->length = NGX_SPDY_SETTINGS_NUM_SIZE + + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE; +#endif + frame->priority = NGX_SPDY_HIGHEST_PRIORITY; + frame->blocked = 0; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_SETTINGS); + p = ngx_spdy_frame_write_flags_and_len(p, NGX_SPDY_FLAG_CLEAR_SETTINGS, + NGX_SPDY_SETTINGS_NUM_SIZE + + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE); + + p = ngx_spdy_frame_aligned_write_uint32(p, 2); + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + p = ngx_spdy_frame_write_flags_and_id(p, 0, NGX_SPDY_SETTINGS_MAX_STREAMS); + p = ngx_spdy_frame_aligned_write_uint32(p, sscf->concurrent_streams); + + p = ngx_spdy_frame_write_flags_and_id(p, 0, NGX_SPDY_SETTINGS_INIT_WINDOW); + p = ngx_spdy_frame_aligned_write_uint32(p, NGX_SPDY_STREAM_WINDOW); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_http_spdy_settings_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + ngx_free_chain(sc->pool, frame->first); + + return NGX_OK; +} + + +static ngx_http_spdy_out_frame_t * +ngx_http_spdy_get_ctl_frame(ngx_http_spdy_connection_t *sc, size_t length, + ngx_uint_t priority) +{ + ngx_chain_t *cl; + ngx_http_spdy_out_frame_t *frame; + + frame = sc->free_ctl_frames; + + if (frame) { + sc->free_ctl_frames = frame->next; + + cl = frame->first; + cl->buf->pos = cl->buf->start; + + } else { + frame = ngx_palloc(sc->pool, sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NULL; + } + + cl = ngx_alloc_chain_link(sc->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = ngx_create_temp_buf(sc->pool, + NGX_SPDY_CTL_FRAME_BUFFER_SIZE); + if (cl->buf == NULL) { + return NULL; + } + + cl->buf->last_buf = 1; + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_spdy_ctl_frame_handler; + frame->stream = NULL; + } + +#if (NGX_DEBUG) + if (length > NGX_SPDY_CTL_FRAME_BUFFER_SIZE - NGX_SPDY_FRAME_HEADER_SIZE) { + ngx_log_error(NGX_LOG_ALERT, sc->pool->log, 0, + "requested control frame is too large: %uz", length); + return NULL; + } + + frame->length = length; +#endif + + frame->priority = priority; + frame->blocked = 0; + + return frame; +} + + +static ngx_int_t +ngx_http_spdy_ctl_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + frame->next = sc->free_ctl_frames; + sc->free_ctl_frames = frame; + + return NGX_OK; +} + + +static ngx_http_spdy_stream_t * +ngx_http_spdy_create_stream(ngx_http_spdy_connection_t *sc, ngx_uint_t id, + ngx_uint_t priority) +{ + ngx_log_t *log; + ngx_uint_t index; + ngx_event_t *rev, *wev; + ngx_connection_t *fc; + ngx_http_log_ctx_t *ctx; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_core_srv_conf_t *cscf; + ngx_http_spdy_srv_conf_t *sscf; + + fc = sc->free_fake_connections; + + if (fc) { + sc->free_fake_connections = fc->data; + + rev = fc->read; + wev = fc->write; + log = fc->log; + ctx = log->data; + + } else { + fc = ngx_palloc(sc->pool, sizeof(ngx_connection_t)); + if (fc == NULL) { + return NULL; + } + + rev = ngx_palloc(sc->pool, sizeof(ngx_event_t)); + if (rev == NULL) { + return NULL; + } + + wev = ngx_palloc(sc->pool, sizeof(ngx_event_t)); + if (wev == NULL) { + return NULL; + } + + log = ngx_palloc(sc->pool, sizeof(ngx_log_t)); + if (log == NULL) { + return NULL; + } + + ctx = ngx_palloc(sc->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + return NULL; + } + + ctx->connection = fc; + ctx->request = NULL; + } + + ngx_memcpy(log, sc->connection->log, sizeof(ngx_log_t)); + + log->data = ctx; + + ngx_memzero(rev, sizeof(ngx_event_t)); + + rev->data = fc; + rev->ready = 1; + rev->handler = ngx_http_spdy_close_stream_handler; + rev->log = log; + + ngx_memcpy(wev, rev, sizeof(ngx_event_t)); + + wev->write = 1; + + ngx_memcpy(fc, sc->connection, sizeof(ngx_connection_t)); + + fc->data = sc->http_connection; + fc->read = rev; + fc->write = wev; + fc->sent = 0; + fc->log = log; + fc->buffered = 0; + fc->sndlowat = 1; + fc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; + + r = ngx_http_create_request(fc); + if (r == NULL) { + return NULL; + } + + r->valid_location = 1; + + fc->data = r; + sc->connection->requests++; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_in = ngx_create_temp_buf(r->pool, + cscf->client_header_buffer_size); + if (r->header_in == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; + + stream = ngx_pcalloc(r->pool, sizeof(ngx_http_spdy_stream_t)); + if (stream == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->spdy_stream = stream; + + stream->id = id; + stream->request = r; + stream->connection = sc; + + stream->send_window = sc->init_window; + stream->recv_window = NGX_SPDY_STREAM_WINDOW; + + stream->priority = priority; + + sscf = ngx_http_get_module_srv_conf(r, ngx_http_spdy_module); + + index = ngx_http_spdy_stream_index(sscf, id); + + stream->index = sc->streams_index[index]; + sc->streams_index[index] = stream; + + sc->processing++; + + return stream; +} + + +static ngx_http_spdy_stream_t * +ngx_http_spdy_get_stream_by_id(ngx_http_spdy_connection_t *sc, + ngx_uint_t sid) +{ + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_srv_conf_t *sscf; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + stream = sc->streams_index[ngx_http_spdy_stream_index(sscf, sid)]; + + while (stream) { + if (stream->id == sid) { + return stream; + } + + stream = stream->index; + } + + return NULL; +} + + +static ngx_int_t +ngx_http_spdy_parse_header(ngx_http_request_t *r) +{ + u_char *p, *end, ch; + ngx_uint_t hash; + ngx_http_core_srv_conf_t *cscf; + + enum { + sw_name_len = 0, + sw_name, + sw_value_len, + sw_value + } state; + + state = r->state; + + p = r->header_in->pos; + end = r->header_in->last; + + switch (state) { + + case sw_name_len: + + if (end - p < NGX_SPDY_NV_NLEN_SIZE) { + return NGX_AGAIN; + } + + r->lowcase_index = ngx_spdy_frame_parse_uint32(p); + + if (r->lowcase_index == 0) { + return NGX_ERROR; + } + + /* null-terminate the previous header value */ + *p = '\0'; + + p += NGX_SPDY_NV_NLEN_SIZE; + + r->invalid_header = 0; + + state = sw_name; + + /* fall through */ + + case sw_name: + + if ((ngx_uint_t) (end - p) < r->lowcase_index) { + break; + } + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_name_start = p; + r->header_name_end = p + r->lowcase_index; + + if (p[0] == ':') { + p++; + } + + hash = 0; + + for ( /* void */ ; p != r->header_name_end; p++) { + + ch = *p; + + hash = ngx_hash(hash, ch); + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + switch (ch) { + case '\0': + case LF: + case CR: + case ':': + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%*s\"", + r->lowcase_index, r->header_name_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (ch >= 'A' && ch <= 'Z') { + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + r->header_hash = hash; + + state = sw_value_len; + + /* fall through */ + + case sw_value_len: + + if (end - p < NGX_SPDY_NV_VLEN_SIZE) { + break; + } + + r->lowcase_index = ngx_spdy_frame_parse_uint32(p); + + /* null-terminate header name */ + *p = '\0'; + + p += NGX_SPDY_NV_VLEN_SIZE; + + state = sw_value; + + /* fall through */ + + case sw_value: + + if ((ngx_uint_t) (end - p) < r->lowcase_index) { + break; + } + + r->header_start = p; + + while (r->lowcase_index--) { + ch = *p; + + if (ch == '\0') { + + if (p == r->header_start) { + return NGX_ERROR; + } + + r->header_end = p; + r->header_in->pos = p + 1; + + r->state = sw_value; + + return NGX_OK; + } + + if (ch == CR || ch == LF) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%*s\" with " + "invalid value: \"%*s\\%c...\"", + r->header_name_end - r->header_name_start, + r->header_name_start, + p - r->header_start, + r->header_start, + ch == CR ? 'r' : 'n'); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + p++; + } + + r->header_end = p; + r->header_in->pos = p; + + r->state = 0; + + return NGX_DONE; + } + + r->header_in->pos = p; + r->state = state; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_spdy_alloc_large_header_buffer(ngx_http_request_t *r) +{ + u_char *old, *new, *p; + size_t rest; + ngx_buf_t *buf; + ngx_http_spdy_stream_t *stream; + ngx_http_core_srv_conf_t *cscf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy alloc large header buffer"); + + stream = r->spdy_stream; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (stream->header_buffers + == (ngx_uint_t) cscf->large_client_header_buffers.num) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too large request"); + + return NGX_DECLINED; + } + + rest = r->header_in->last - r->header_in->pos; + + /* + * One more byte is needed for null-termination + * and another one for further progress. + */ + if (rest > cscf->large_client_header_buffers.size - 2) { + p = r->header_in->pos; + + if (rest > NGX_MAX_ERROR_STR - 300) { + rest = NGX_MAX_ERROR_STR - 300; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too long header name or value: \"%*s...\"", + rest, p); + + return NGX_DECLINED; + } + + buf = ngx_create_temp_buf(r->pool, cscf->large_client_header_buffers.size); + if (buf == NULL) { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy large header alloc: %p %uz", + buf->pos, buf->end - buf->last); + + old = r->header_in->pos; + new = buf->pos; + + if (rest) { + buf->last = ngx_cpymem(new, old, rest); + } + + r->header_in = buf; + + stream->header_buffers++; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_handle_request_header(ngx_http_request_t *r) +{ + ngx_uint_t i; + ngx_table_elt_t *h; + ngx_http_core_srv_conf_t *cscf; + ngx_http_spdy_request_header_t *sh; + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%*s\"", + r->header_end - r->header_name_start, + r->header_name_start); + return NGX_OK; + } + + } + + if (r->header_name_start[0] == ':') { + r->header_name_start++; + + for (i = 0; i < NGX_SPDY_REQUEST_HEADERS; i++) { + sh = &ngx_http_spdy_request_headers[i]; + + if (sh->hash != r->header_hash + || sh->len != r->header_name_end - r->header_name_start + || ngx_strncmp(sh->header, r->header_name_start, sh->len) != 0) + { + continue; + } + + return sh->handler(r); + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \":%*s\"", + r->header_end - r->header_name_start, + r->header_name_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = r->header_hash; + + h->key.len = r->header_name_end - r->header_name_start; + h->key.data = r->header_name_start; + + h->value.len = r->header_end - r->header_start; + h->value.data = r->header_start; + + h->lowcase_key = h->key.data; + + return NGX_OK; +} + + +void +ngx_http_spdy_request_headers_init(void) +{ + ngx_uint_t i; + ngx_http_spdy_request_header_t *h; + + for (i = 0; i < NGX_SPDY_REQUEST_HEADERS; i++) { + h = &ngx_http_spdy_request_headers[i]; + h->hash = ngx_hash_key(h->header, h->len); + } +} + + +static ngx_int_t +ngx_http_spdy_parse_method(ngx_http_request_t *r) +{ + size_t k, len; + ngx_uint_t n; + const u_char *p, *m; + + /* + * This array takes less than 256 sequential bytes, + * and if typical CPU cache line size is 64 bytes, + * it is prefetched for 4 load operations. + */ + static const struct { + u_char len; + const u_char method[11]; + uint32_t value; + } tests[] = { + { 3, "GET", NGX_HTTP_GET }, + { 4, "POST", NGX_HTTP_POST }, + { 4, "HEAD", NGX_HTTP_HEAD }, + { 7, "OPTIONS", NGX_HTTP_OPTIONS }, + { 8, "PROPFIND", NGX_HTTP_PROPFIND }, + { 3, "PUT", NGX_HTTP_PUT }, + { 5, "MKCOL", NGX_HTTP_MKCOL }, + { 6, "DELETE", NGX_HTTP_DELETE }, + { 4, "COPY", NGX_HTTP_COPY }, + { 4, "MOVE", NGX_HTTP_MOVE }, + { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, + { 4, "LOCK", NGX_HTTP_LOCK }, + { 6, "UNLOCK", NGX_HTTP_UNLOCK }, + { 5, "PATCH", NGX_HTTP_PATCH }, + { 5, "TRACE", NGX_HTTP_TRACE } + }, *test; + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :method header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + len = r->header_end - r->header_start; + + r->method_name.len = len; + r->method_name.data = r->header_start; + + test = tests; + n = sizeof(tests) / sizeof(tests[0]); + + do { + if (len == test->len) { + p = r->method_name.data; + m = test->method; + k = len; + + do { + if (*p++ != *m++) { + goto next; + } + } while (--k); + + r->method = test->value; + return NGX_OK; + } + + next: + test++; + + } while (--n); + + p = r->method_name.data; + + do { + if ((*p < 'A' || *p > 'Z') && *p != '_') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", + &r->method_name); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + p++; + + } while (--len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_scheme(ngx_http_request_t *r) +{ + if (r->schema_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :schema header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + r->schema_start = r->header_start; + r->schema_end = r->header_end; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_host(ngx_http_request_t *r) +{ + ngx_table_elt_t *h; + + if (r->headers_in.host) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :host header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + r->headers_in.host = h; + + h->hash = r->header_hash; + + h->key.len = r->header_name_end - r->header_name_start; + h->key.data = r->header_name_start; + + h->value.len = r->header_end - r->header_start; + h->value.data = r->header_start; + + h->lowcase_key = h->key.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_path(ngx_http_request_t *r) +{ + if (r->unparsed_uri.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :path header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + r->uri_start = r->header_start; + r->uri_end = r->header_end; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid URI: \"%*s\"", + r->uri_end - r->uri_start, r->uri_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_request_uri() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_version(ngx_http_request_t *r) +{ + u_char *p, ch; + + if (r->http_protocol.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :version header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + p = r->header_start; + + if (r->header_end - p < 8 || !(ngx_str5cmp(p, 'H', 'T', 'T', 'P', '/'))) { + goto invalid; + } + + ch = *(p + 5); + + if (ch < '1' || ch > '9') { + goto invalid; + } + + r->http_major = ch - '0'; + + for (p += 6; p != r->header_end - 2; p++) { + + ch = *p; + + if (ch == '.') { + break; + } + + if (ch < '0' || ch > '9') { + goto invalid; + } + + r->http_major = r->http_major * 10 + ch - '0'; + } + + if (*p != '.') { + goto invalid; + } + + ch = *(p + 1); + + if (ch < '0' || ch > '9') { + goto invalid; + } + + r->http_minor = ch - '0'; + + for (p += 2; p != r->header_end; p++) { + + ch = *p; + + if (ch < '0' || ch > '9') { + goto invalid; + } + + r->http_minor = r->http_minor * 10 + ch - '0'; + } + + r->http_protocol.len = r->header_end - r->header_start; + r->http_protocol.data = r->header_start; + r->http_version = r->http_major * 1000 + r->http_minor; + + return NGX_OK; + +invalid: + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid http version: \"%*s\"", + r->header_end - r->header_start, r->header_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; +} + + +static ngx_int_t +ngx_http_spdy_construct_request_line(ngx_http_request_t *r) +{ + u_char *p; + + if (r->method_name.len == 0 + || r->unparsed_uri.len == 0 + || r->http_protocol.len == 0) + { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + r->request_line.len = r->method_name.len + 1 + + r->unparsed_uri.len + 1 + + r->http_protocol.len; + + p = ngx_pnalloc(r->pool, r->request_line.len + 1); + if (p == NULL) { + ngx_http_spdy_close_stream(r->spdy_stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + + *p++ = ' '; + + p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len); + + *p++ = ' '; + + ngx_memcpy(p, r->http_protocol.data, r->http_protocol.len + 1); + + /* some modules expect the space character after method name */ + r->method_name.data = r->request_line.data; + + return NGX_OK; +} + + +static void +ngx_http_spdy_run_request(ngx_http_request_t *r) +{ + ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + if (ngx_http_spdy_construct_request_line(r) != NGX_OK) { + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy http request line: \"%V\"", &r->request_line); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + part = &r->headers_in.headers.part; + h = part->elts; + + for (i = 0 ;; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + hh = ngx_hash_find(&cmcf->headers_in_hash, h[i].hash, + h[i].lowcase_key, h[i].key.len); + + if (hh && hh->handler(r, &h[i], hh->offset) != NGX_OK) { + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy http header: \"%V: %V\"", &h[i].key, &h[i].value); + } + + r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; + + if (ngx_http_process_request_header(r) != NGX_OK) { + return; + } + + if (r->headers_in.content_length_n > 0 && r->spdy_stream->in_closed) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + + r->spdy_stream->skip_data = NGX_SPDY_DATA_ERROR; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return; + } + + ngx_http_process_request(r); +} + + +static ngx_int_t +ngx_http_spdy_init_request_body(ngx_http_request_t *r) +{ + ngx_buf_t *buf; + ngx_temp_file_t *tf; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); + if (rb == NULL) { + return NGX_ERROR; + } + + r->request_body = rb; + + if (r->spdy_stream->in_closed) { + return NGX_OK; + } + + rb->rest = r->headers_in.content_length_n; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->request_body_in_file_only + || rb->rest > (off_t) clcf->client_body_buffer_size + || rb->rest < 0) + { + tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)); + if (tf == NULL) { + return NGX_ERROR; + } + + tf->file.fd = NGX_INVALID_FILE; + tf->file.log = r->connection->log; + tf->path = clcf->client_body_temp_path; + tf->pool = r->pool; + tf->warn = "a client request body is buffered to a temporary file"; + tf->log_level = r->request_body_file_log_level; + tf->persistent = r->request_body_in_persistent_file; + tf->clean = r->request_body_in_clean_file; + + if (r->request_body_file_group_access) { + tf->access = 0660; + } + + rb->temp_file = tf; + + if (r->spdy_stream->in_closed + && ngx_create_temp_file(&tf->file, tf->path, tf->pool, + tf->persistent, tf->clean, tf->access) + != NGX_OK) + { + return NGX_ERROR; + } + + buf = ngx_calloc_buf(r->pool); + if (buf == NULL) { + return NGX_ERROR; + } + + } else { + + if (rb->rest == 0) { + return NGX_OK; + } + + buf = ngx_create_temp_buf(r->pool, (size_t) rb->rest); + if (buf == NULL) { + return NGX_ERROR; + } + } + + rb->buf = buf; + + rb->bufs = ngx_alloc_chain_link(r->pool); + if (rb->bufs == NULL) { + return NGX_ERROR; + } + + rb->bufs->buf = buf; + rb->bufs->next = NULL; + + rb->rest = 0; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_spdy_read_request_body(ngx_http_request_t *r, + ngx_http_client_body_handler_pt post_handler) +{ + ngx_http_spdy_stream_t *stream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy read request body"); + + stream = r->spdy_stream; + + switch (stream->skip_data) { + + case NGX_SPDY_DATA_DISCARD: + post_handler(r); + return NGX_OK; + + case NGX_SPDY_DATA_ERROR: + if (r->headers_in.content_length_n == -1) { + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } else { + return NGX_HTTP_BAD_REQUEST; + } + + case NGX_SPDY_DATA_INTERNAL_ERROR: + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (!r->request_body->buf && ngx_http_spdy_init_request_body(r) != NGX_OK) { + stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (stream->in_closed) { + post_handler(r); + return NGX_OK; + } + + r->request_body->post_handler = post_handler; + + r->read_event_handler = ngx_http_test_reading; + r->write_event_handler = ngx_http_request_empty_handler; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_spdy_terminate_stream(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream, ngx_uint_t status) +{ + ngx_event_t *rev; + ngx_connection_t *fc; + + if (ngx_http_spdy_send_rst_stream(sc, stream->id, status, + NGX_SPDY_HIGHEST_PRIORITY) + == NGX_ERROR) + { + return NGX_ERROR; + } + + stream->out_closed = 1; + + fc = stream->request->connection; + fc->error = 1; + + rev = fc->read; + rev->handler(rev); + + return NGX_OK; +} + + +static void +ngx_http_spdy_close_stream_handler(ngx_event_t *ev) +{ + ngx_connection_t *fc; + ngx_http_request_t *r; + + fc = ev->data; + r = fc->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy close stream handler"); + + ngx_http_spdy_close_stream(r->spdy_stream, 0); +} + + +void +ngx_http_spdy_close_stream(ngx_http_spdy_stream_t *stream, ngx_int_t rc) +{ + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_spdy_stream_t **index, *s; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_connection_t *sc; + + sc = stream->connection; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy close stream %ui, queued %ui, processing %ui", + stream->id, stream->queued, sc->processing); + + fc = stream->request->connection; + + if (stream->queued) { + fc->write->handler = ngx_http_spdy_close_stream_handler; + return; + } + + if (!stream->out_closed) { + if (ngx_http_spdy_send_rst_stream(sc, stream->id, + NGX_SPDY_INTERNAL_ERROR, + stream->priority) + != NGX_OK) + { + sc->connection->error = 1; + } + } + + if (sc->stream == stream) { + sc->stream = NULL; + } + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + index = sc->streams_index + ngx_http_spdy_stream_index(sscf, stream->id); + + for ( ;; ) { + s = *index; + + if (s == NULL) { + break; + } + + if (s == stream) { + *index = s->index; + break; + } + + index = &s->index; + } + + ngx_http_free_request(stream->request, rc); + + ev = fc->read; + + if (ev->active || ev->disabled) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "fake read event was activated"); + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + ev = fc->write; + + if (ev->active || ev->disabled) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "fake write event was activated"); + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + fc->data = sc->free_fake_connections; + sc->free_fake_connections = fc; + + sc->processing--; + + if (sc->processing || sc->blocked) { + return; + } + + ev = sc->connection->read; + + ev->handler = ngx_http_spdy_handle_connection_handler; + ngx_post_event(ev, &ngx_posted_events); +} + + +static void +ngx_http_spdy_handle_connection_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + + rev->handler = ngx_http_spdy_read_handler; + + if (rev->ready) { + ngx_http_spdy_read_handler(rev); + return; + } + + c = rev->data; + + ngx_http_spdy_handle_connection(c->data); +} + + +static void +ngx_http_spdy_keepalive_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_connection_t *sc; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy keepalive handler"); + + if (rev->timedout || c->close) { + ngx_http_close_connection(c); + return; + } + +#if (NGX_HAVE_KQUEUE) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + if (rev->pending_eof) { + c->log->handler = NULL; + ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno, + "kevent() reported that client %V closed " + "keepalive connection", &c->addr_text); +#if (NGX_HTTP_SSL) + if (c->ssl) { + c->ssl->no_send_shutdown = 1; + } +#endif + ngx_http_close_connection(c); + return; + } + } + +#endif + + c->destroyed = 0; + c->idle = 0; + ngx_reusable_connection(c, 0); + + sc = c->data; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + sc->pool = ngx_create_pool(sscf->pool_size, sc->connection->log); + if (sc->pool == NULL) { + ngx_http_close_connection(c); + return; + } + + sc->streams_index = ngx_pcalloc(sc->pool, + ngx_http_spdy_streams_index_size(sscf) + * sizeof(ngx_http_spdy_stream_t *)); + if (sc->streams_index == NULL) { + ngx_http_close_connection(c); + return; + } + + c->write->handler = ngx_http_spdy_write_handler; + + rev->handler = ngx_http_spdy_read_handler; + ngx_http_spdy_read_handler(rev); +} + + +static void +ngx_http_spdy_finalize_connection(ngx_http_spdy_connection_t *sc, + ngx_int_t rc) +{ + ngx_uint_t i, size; + ngx_event_t *ev; + ngx_connection_t *c, *fc; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_srv_conf_t *sscf; + + c = sc->connection; + + if (!sc->processing) { + ngx_http_close_connection(c); + return; + } + + c->error = 1; + c->read->handler = ngx_http_empty_handler; + c->write->handler = ngx_http_empty_handler; + + sc->last_out = NULL; + + sc->blocked = 1; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + size = ngx_http_spdy_streams_index_size(sscf); + + for (i = 0; i < size; i++) { + stream = sc->streams_index[i]; + + while (stream) { + stream->handled = 0; + + r = stream->request; + fc = r->connection; + + fc->error = 1; + + if (stream->queued) { + stream->queued = 0; + + ev = fc->write; + ev->delayed = 0; + + } else { + ev = fc->read; + } + + stream = stream->index; + + ev->eof = 1; + ev->handler(ev); + } + } + + sc->blocked = 0; + + if (sc->processing) { + return; + } + + ngx_http_close_connection(c); +} + + +static ngx_int_t +ngx_http_spdy_adjust_windows(ngx_http_spdy_connection_t *sc, ssize_t delta) +{ + ngx_uint_t i, size; + ngx_event_t *wev; + ngx_http_spdy_stream_t *stream, *sn; + ngx_http_spdy_srv_conf_t *sscf; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + size = ngx_http_spdy_streams_index_size(sscf); + + for (i = 0; i < size; i++) { + + for (stream = sc->streams_index[i]; stream; stream = sn) { + sn = stream->index; + + if (delta > 0 + && stream->send_window + > (ssize_t) (NGX_SPDY_MAX_WINDOW - delta)) + { + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_FLOW_CONTROL_ERROR) + == NGX_ERROR) + { + return NGX_ERROR; + } + + continue; + } + + stream->send_window += delta; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui adjust window:%z", + stream->id, stream->send_window); + + if (stream->send_window > 0 && stream->exhausted) { + stream->exhausted = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + } + } + } + } + + return NGX_OK; +} + + +static void +ngx_http_spdy_pool_cleanup(void *data) +{ + ngx_http_spdy_connection_t *sc = data; + + if (sc->pool) { + ngx_destroy_pool(sc->pool); + } +} + + +static void * +ngx_http_spdy_zalloc(void *opaque, u_int items, u_int size) +{ + ngx_http_spdy_connection_t *sc = opaque; + + return ngx_palloc(sc->connection->pool, items * size); +} + + +static void +ngx_http_spdy_zfree(void *opaque, void *address) +{ +#if 0 + ngx_http_spdy_connection_t *sc = opaque; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy zfree: %p", address); +#endif +} diff -uNr a/src/http/ngx_http_spdy_filter_module.c b/src/http/ngx_http_spdy_filter_module.c --- a/src/http/ngx_http_spdy_filter_module.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/ngx_http_spdy_filter_module.c 2020-01-03 11:57:43.000000000 +0800 @@ -0,0 +1,1222 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include +#include + +#include + + +#define ngx_http_spdy_nv_nsize(h) (NGX_SPDY_NV_NLEN_SIZE + sizeof(h) - 1) +#define ngx_http_spdy_nv_vsize(h) (NGX_SPDY_NV_VLEN_SIZE + sizeof(h) - 1) + +#define ngx_http_spdy_nv_write_num ngx_spdy_frame_write_uint32 +#define ngx_http_spdy_nv_write_nlen ngx_spdy_frame_write_uint32 +#define ngx_http_spdy_nv_write_vlen ngx_spdy_frame_write_uint32 + +#define ngx_http_spdy_nv_write_name(p, h) \ + ngx_cpymem(ngx_http_spdy_nv_write_nlen(p, sizeof(h) - 1), h, sizeof(h) - 1) + +#define ngx_http_spdy_nv_write_val(p, h) \ + ngx_cpymem(ngx_http_spdy_nv_write_vlen(p, sizeof(h) - 1), h, sizeof(h) - 1) + + +static ngx_chain_t *ngx_http_spdy_send_chain(ngx_connection_t *fc, + ngx_chain_t *in, off_t limit); + +static ngx_inline ngx_int_t ngx_http_spdy_filter_send( + ngx_connection_t *fc, ngx_http_spdy_stream_t *stream); +static ngx_inline ngx_int_t ngx_http_spdy_flow_control( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_stream_t *stream); +static void ngx_http_spdy_waiting_queue(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream); + +static ngx_chain_t *ngx_http_spdy_filter_get_shadow( + ngx_http_spdy_stream_t *stream, ngx_buf_t *buf, off_t offset, off_t size); +static ngx_http_spdy_out_frame_t *ngx_http_spdy_filter_get_data_frame( + ngx_http_spdy_stream_t *stream, size_t len, ngx_chain_t *first, + ngx_chain_t *last); + +static ngx_int_t ngx_http_spdy_syn_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); +static ngx_int_t ngx_http_spdy_data_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); +static ngx_inline void ngx_http_spdy_handle_frame( + ngx_http_spdy_stream_t *stream, ngx_http_spdy_out_frame_t *frame); +static ngx_inline void ngx_http_spdy_handle_stream( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_stream_t *stream); + +static void ngx_http_spdy_filter_cleanup(void *data); + +static ngx_int_t ngx_http_spdy_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_spdy_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_spdy_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_spdy_filter_module = { + NGX_MODULE_V1, + &ngx_http_spdy_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; + + +static ngx_int_t +ngx_http_spdy_header_filter(ngx_http_request_t *r) +{ + int rc; + size_t len; + u_char *p, *buf, *last; + ngx_buf_t *b; + ngx_str_t host; + ngx_uint_t i, j, count, port; + ngx_chain_t *cl; + ngx_list_part_t *part, *pt; + ngx_table_elt_t *header, *h; + ngx_connection_t *c; + ngx_http_cleanup_t *cln; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_out_frame_t *frame; + ngx_http_spdy_connection_t *sc; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + u_char addr[NGX_SOCKADDR_STRLEN]; + + if (!r->spdy_stream) { + return ngx_http_next_header_filter(r); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy header filter"); + + if (r->header_sent) { + return NGX_OK; + } + + r->header_sent = 1; + + if (r != r->main) { + return NGX_OK; + } + + c = r->connection; + + if (r->method == NGX_HTTP_HEAD) { + r->header_only = 1; + } + + switch (r->headers_out.status) { + + case NGX_HTTP_OK: + case NGX_HTTP_PARTIAL_CONTENT: + break; + + case NGX_HTTP_NOT_MODIFIED: + r->header_only = 1; + break; + + case NGX_HTTP_NO_CONTENT: + r->header_only = 1; + + ngx_str_null(&r->headers_out.content_type); + + r->headers_out.content_length = NULL; + r->headers_out.content_length_n = -1; + + /* fall through */ + + default: + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + } + + len = NGX_SPDY_NV_NUM_SIZE + + ngx_http_spdy_nv_nsize(":version") + + ngx_http_spdy_nv_vsize("HTTP/1.1") + + ngx_http_spdy_nv_nsize(":status") + + (r->headers_out.status_line.len + ? NGX_SPDY_NV_VLEN_SIZE + r->headers_out.status_line.len + : ngx_http_spdy_nv_vsize("418")); + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->headers_out.server == NULL) { + len += ngx_http_spdy_nv_nsize("server"); + len += clcf->server_tokens ? ngx_http_spdy_nv_vsize(NGINX_VER) + : ngx_http_spdy_nv_vsize("nginx"); + } + + if (r->headers_out.date == NULL) { + len += ngx_http_spdy_nv_nsize("date") + + ngx_http_spdy_nv_vsize("Wed, 31 Dec 1986 10:00:00 GMT"); + } + + if (r->headers_out.content_type.len) { + len += ngx_http_spdy_nv_nsize("content-type") + + NGX_SPDY_NV_VLEN_SIZE + r->headers_out.content_type.len; + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + len += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + len += ngx_http_spdy_nv_nsize("content-length") + + NGX_SPDY_NV_VLEN_SIZE + NGX_OFF_T_LEN; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + len += ngx_http_spdy_nv_nsize("last-modified") + + ngx_http_spdy_nv_vsize("Wed, 31 Dec 1986 10:00:00 GMT"); + } + + if (r->headers_out.location + && r->headers_out.location->value.len + && r->headers_out.location->value.data[0] == '/') + { + r->headers_out.location->hash = 0; + + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.len = NGX_SOCKADDR_STRLEN; + host.data = addr; + + if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + switch (c->local_sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) c->local_sockaddr; + port = ntohs(sin6->sin6_port); + break; +#endif +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + port = 0; + break; +#endif + default: /* AF_INET */ + sin = (struct sockaddr_in *) c->local_sockaddr; + port = ntohs(sin->sin_port); + break; + } + + len += ngx_http_spdy_nv_nsize("location") + + ngx_http_spdy_nv_vsize("https://") + + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + +#if (NGX_HTTP_SSL) + if (c->ssl) + port = (port == 443) ? 0 : port; + else +#endif + port = (port == 80) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + len += sizeof(":65535") - 1; + } + + } else { + ngx_str_null(&host); + port = 0; + } + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += NGX_SPDY_NV_NLEN_SIZE + header[i].key.len + + NGX_SPDY_NV_VLEN_SIZE + header[i].value.len; + } + + buf = ngx_alloc(len, r->pool->log); + if (buf == NULL) { + return NGX_ERROR; + } + + last = buf + NGX_SPDY_NV_NUM_SIZE; + + last = ngx_http_spdy_nv_write_name(last, ":version"); + last = ngx_http_spdy_nv_write_val(last, "HTTP/1.1"); + + last = ngx_http_spdy_nv_write_name(last, ":status"); + + if (r->headers_out.status_line.len) { + last = ngx_http_spdy_nv_write_vlen(last, + r->headers_out.status_line.len); + last = ngx_cpymem(last, r->headers_out.status_line.data, + r->headers_out.status_line.len); + } else { + last = ngx_http_spdy_nv_write_vlen(last, 3); + last = ngx_sprintf(last, "%03ui", r->headers_out.status); + } + + count = 2; + + if (r->headers_out.server == NULL) { + last = ngx_http_spdy_nv_write_name(last, "server"); + last = clcf->server_tokens + ? ngx_http_spdy_nv_write_val(last, NGINX_VER) + : ngx_http_spdy_nv_write_val(last, "nginx"); + + count++; + } + + if (r->headers_out.date == NULL) { + last = ngx_http_spdy_nv_write_name(last, "date"); + + last = ngx_http_spdy_nv_write_vlen(last, ngx_cached_http_time.len); + + last = ngx_cpymem(last, ngx_cached_http_time.data, + ngx_cached_http_time.len); + + count++; + } + + if (r->headers_out.content_type.len) { + + last = ngx_http_spdy_nv_write_name(last, "content-type"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_cpymem(p, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + last = ngx_cpymem(last, "; charset=", sizeof("; charset=") - 1); + + last = ngx_cpymem(last, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* update r->headers_out.content_type for possible logging */ + + r->headers_out.content_type.len = last - p; + r->headers_out.content_type.data = p; + } + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + r->headers_out.content_type.len); + + count++; + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + last = ngx_http_spdy_nv_write_name(last, "content-length"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_sprintf(p, "%O", r->headers_out.content_length_n); + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + last - p); + + count++; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + last = ngx_http_spdy_nv_write_name(last, "last-modified"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_http_time(p, r->headers_out.last_modified_time); + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + last - p); + + count++; + } + + if (host.data) { + + last = ngx_http_spdy_nv_write_name(last, "location"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_cpymem(p, "http", sizeof("http") - 1); + +#if (NGX_HTTP_SSL) + if (c->ssl) { + *last++ ='s'; + } +#endif + + *last++ = ':'; *last++ = '/'; *last++ = '/'; + + last = ngx_cpymem(last, host.data, host.len); + + if (port) { + last = ngx_sprintf(last, ":%ui", port); + } + + last = ngx_cpymem(last, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = last - p; + r->headers_out.location->value.data = p; + ngx_str_set(&r->headers_out.location->key, "location"); + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + r->headers_out.location->value.len); + + count++; + } + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0 || header[i].hash == 2) { + continue; + } + + last = ngx_http_spdy_nv_write_nlen(last, header[i].key.len); + + ngx_strlow(last, header[i].key.data, header[i].key.len); + last += header[i].key.len; + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_cpymem(p, header[i].value.data, header[i].value.len); + + pt = part; + h = header; + + for (j = i + 1; /* void */; j++) { + + if (j >= pt->nelts) { + if (pt->next == NULL) { + break; + } + + pt = pt->next; + h = pt->elts; + j = 0; + } + + if (h[j].hash == 0 || h[j].hash == 2 + || h[j].key.len != header[i].key.len + || ngx_strncasecmp(header[i].key.data, h[j].key.data, + header[i].key.len)) + { + continue; + } + + if (h[j].value.len) { + if (last != p) { + *last++ = '\0'; + } + + last = ngx_cpymem(last, h[j].value.data, h[j].value.len); + } + + h[j].hash = 2; + } + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + last - p); + + count++; + } + + (void) ngx_http_spdy_nv_write_num(buf, count); + + stream = r->spdy_stream; + sc = stream->connection; + + len = last - buf; + + b = ngx_create_temp_buf(r->pool, NGX_SPDY_FRAME_HEADER_SIZE + + NGX_SPDY_SYN_REPLY_SIZE + + deflateBound(&sc->zstream_out, len)); + if (b == NULL) { + ngx_free(buf); + return NGX_ERROR; + } + + b->last += NGX_SPDY_FRAME_HEADER_SIZE + NGX_SPDY_SYN_REPLY_SIZE; + + sc->zstream_out.next_in = buf; + sc->zstream_out.avail_in = len; + sc->zstream_out.next_out = b->last; + sc->zstream_out.avail_out = b->end - b->last; + + rc = deflate(&sc->zstream_out, Z_SYNC_FLUSH); + + ngx_free(buf); + + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "deflate() failed: %d", rc); + return NGX_ERROR; + } + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_out.next_in, sc->zstream_out.next_out, + sc->zstream_out.avail_in, sc->zstream_out.avail_out, + rc); + + b->last = sc->zstream_out.next_out; + + p = b->pos; + p = ngx_spdy_frame_write_head(p, NGX_SPDY_SYN_REPLY); + + len = b->last - b->pos; + + r->header_size = len; + + len -= NGX_SPDY_FRAME_HEADER_SIZE; + + if (r->header_only) { + b->last_buf = 1; + p = ngx_spdy_frame_write_flags_and_len(p, NGX_SPDY_FLAG_FIN, len); + + } else { + p = ngx_spdy_frame_write_flags_and_len(p, 0, len); + } + + (void) ngx_spdy_frame_write_sid(p, stream->id); + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + frame = ngx_palloc(r->pool, sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_spdy_syn_frame_handler; + frame->stream = stream; + frame->length = len; + frame->priority = stream->priority; + frame->blocked = 1; + frame->fin = r->header_only; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0, + "spdy:%ui create SYN_REPLY frame %p: len:%uz", + stream->id, frame, frame->length); + + ngx_http_spdy_queue_blocked_frame(sc, frame); + + cln = ngx_http_cleanup_add(r, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_spdy_filter_cleanup; + cln->data = stream; + + stream->queued = 1; + + c->send_chain = ngx_http_spdy_send_chain; + c->need_last_buf = 1; + + return ngx_http_spdy_filter_send(c, stream); +} + + +static ngx_chain_t * +ngx_http_spdy_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) +{ + off_t size, offset; + size_t rest, frame_size; + ngx_chain_t *cl, *out, **ln; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_loc_conf_t *slcf; + ngx_http_spdy_out_frame_t *frame; + ngx_http_spdy_connection_t *sc; + + r = fc->data; + stream = r->spdy_stream; + +#if (NGX_SUPPRESS_WARN) + size = 0; +#endif + + while (in) { + size = ngx_buf_size(in->buf); + + if (size || in->buf->last_buf) { + break; + } + + in = in->next; + } + + if (in == NULL) { + + if (stream->queued) { + fc->write->delayed = 1; + } else { + fc->buffered &= ~NGX_SPDY_BUFFERED; + } + + return NULL; + } + + sc = stream->connection; + + if (size && ngx_http_spdy_flow_control(sc, stream) == NGX_DECLINED) { + fc->write->delayed = 1; + return in; + } + + if (limit == 0 || limit > (off_t) sc->send_window) { + limit = sc->send_window; + } + + if (limit > stream->send_window) { + limit = (stream->send_window > 0) ? stream->send_window : 0; + } + + if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) { + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = in->buf; + in->buf = cl->buf->shadow; + + offset = ngx_buf_in_memory(in->buf) + ? (cl->buf->pos - in->buf->pos) + : (cl->buf->file_pos - in->buf->file_pos); + + cl->next = stream->free_bufs; + stream->free_bufs = cl; + + } else { + offset = 0; + } + +#if (NGX_SUPPRESS_WARN) + cl = NULL; +#endif + + slcf = ngx_http_get_module_loc_conf(r, ngx_http_spdy_module); + + frame_size = (limit <= (off_t) slcf->chunk_size) ? (size_t) limit + : slcf->chunk_size; + + for ( ;; ) { + ln = &out; + rest = frame_size; + + while ((off_t) rest >= size) { + + if (offset) { + cl = ngx_http_spdy_filter_get_shadow(stream, in->buf, + offset, size); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + offset = 0; + + } else { + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = in->buf; + } + + *ln = cl; + ln = &cl->next; + + rest -= (size_t) size; + in = in->next; + + if (in == NULL) { + frame_size -= rest; + rest = 0; + break; + } + + size = ngx_buf_size(in->buf); + } + + if (rest) { + cl = ngx_http_spdy_filter_get_shadow(stream, in->buf, + offset, rest); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf->flush = 0; + cl->buf->last_buf = 0; + + *ln = cl; + + offset += rest; + size -= rest; + } + + frame = ngx_http_spdy_filter_get_data_frame(stream, frame_size, + out, cl); + if (frame == NULL) { + return NGX_CHAIN_ERROR; + } + + ngx_http_spdy_queue_frame(sc, frame); + + sc->send_window -= frame_size; + + stream->send_window -= frame_size; + stream->queued++; + + if (in == NULL) { + break; + } + + limit -= frame_size; + + if (limit == 0) { + break; + } + + if (limit < (off_t) slcf->chunk_size) { + frame_size = (size_t) limit; + } + } + + if (offset) { + cl = ngx_http_spdy_filter_get_shadow(stream, in->buf, offset, size); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + in->buf = cl->buf; + ngx_free_chain(r->pool, cl); + } + + if (ngx_http_spdy_filter_send(fc, stream) == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + if (in && ngx_http_spdy_flow_control(sc, stream) == NGX_DECLINED) { + fc->write->delayed = 1; + } + + return in; +} + + +static ngx_chain_t * +ngx_http_spdy_filter_get_shadow(ngx_http_spdy_stream_t *stream, ngx_buf_t *buf, + off_t offset, off_t size) +{ + ngx_buf_t *chunk; + ngx_chain_t *cl; + + cl = ngx_chain_get_free_buf(stream->request->pool, &stream->free_bufs); + if (cl == NULL) { + return NULL; + } + + chunk = cl->buf; + + ngx_memcpy(chunk, buf, sizeof(ngx_buf_t)); + + chunk->tag = (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow; + chunk->shadow = buf; + + if (ngx_buf_in_memory(chunk)) { + chunk->pos += offset; + chunk->last = chunk->pos + size; + } + + if (chunk->in_file) { + chunk->file_pos += offset; + chunk->file_last = chunk->file_pos + size; + } + + return cl; +} + + +static ngx_http_spdy_out_frame_t * +ngx_http_spdy_filter_get_data_frame(ngx_http_spdy_stream_t *stream, + size_t len, ngx_chain_t *first, ngx_chain_t *last) +{ + u_char *p; + ngx_buf_t *buf; + ngx_uint_t flags; + ngx_chain_t *cl; + ngx_http_spdy_out_frame_t *frame; + + + frame = stream->free_frames; + + if (frame) { + stream->free_frames = frame->next; + + } else { + frame = ngx_palloc(stream->request->pool, + sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NULL; + } + } + + flags = last->buf->last_buf ? NGX_SPDY_FLAG_FIN : 0; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0, + "spdy:%ui create DATA frame %p: len:%uz flags:%ui", + stream->id, frame, len, flags); + + cl = ngx_chain_get_free_buf(stream->request->pool, + &stream->free_data_headers); + if (cl == NULL) { + return NULL; + } + + buf = cl->buf; + + if (buf->start) { + p = buf->start; + buf->pos = p; + + p += NGX_SPDY_SID_SIZE; + + (void) ngx_spdy_frame_write_flags_and_len(p, flags, len); + + } else { + p = ngx_palloc(stream->request->pool, NGX_SPDY_FRAME_HEADER_SIZE); + if (p == NULL) { + return NULL; + } + + buf->pos = p; + buf->start = p; + + p = ngx_spdy_frame_write_sid(p, stream->id); + p = ngx_spdy_frame_write_flags_and_len(p, flags, len); + + buf->last = p; + buf->end = p; + + buf->tag = (ngx_buf_tag_t) &ngx_http_spdy_filter_get_data_frame; + buf->memory = 1; + } + + cl->next = first; + first = cl; + + last->buf->flush = 1; + + frame->first = first; + frame->last = last; + frame->handler = ngx_http_spdy_data_frame_handler; + frame->stream = stream; + frame->length = len; + frame->priority = stream->priority; + frame->blocked = 0; + frame->fin = last->buf->last_buf; + + return frame; +} + + +static ngx_inline ngx_int_t +ngx_http_spdy_filter_send(ngx_connection_t *fc, ngx_http_spdy_stream_t *stream) +{ + stream->blocked = 1; + + if (ngx_http_spdy_send_output_queue(stream->connection) == NGX_ERROR) { + fc->error = 1; + return NGX_ERROR; + } + + stream->blocked = 0; + + if (stream->queued) { + fc->buffered |= NGX_SPDY_BUFFERED; + fc->write->delayed = 1; + return NGX_AGAIN; + } + + fc->buffered &= ~NGX_SPDY_BUFFERED; + + return NGX_OK; +} + + +static ngx_inline ngx_int_t +ngx_http_spdy_flow_control(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream) +{ + if (stream->send_window <= 0) { + stream->exhausted = 1; + return NGX_DECLINED; + } + + if (sc->send_window == 0) { + ngx_http_spdy_waiting_queue(sc, stream); + return NGX_DECLINED; + } + + return NGX_OK; +} + + +static void +ngx_http_spdy_waiting_queue(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream) +{ + ngx_queue_t *q; + ngx_http_spdy_stream_t *s; + + if (stream->handled) { + return; + } + + stream->handled = 1; + + for (q = ngx_queue_last(&sc->waiting); + q != ngx_queue_sentinel(&sc->waiting); + q = ngx_queue_prev(q)) + { + s = ngx_queue_data(q, ngx_http_spdy_stream_t, queue); + + /* + * NB: higher values represent lower priorities. + */ + if (stream->priority >= s->priority) { + break; + } + } + + ngx_queue_insert_after(q, &stream->queue); +} + + +static ngx_int_t +ngx_http_spdy_syn_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + ngx_http_spdy_stream_t *stream; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + stream = frame->stream; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui SYN_REPLY frame %p was sent", stream->id, frame); + + ngx_free_chain(stream->request->pool, frame->first); + + ngx_http_spdy_handle_frame(stream, frame); + + ngx_http_spdy_handle_stream(sc, stream); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_data_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + ngx_chain_t *cl, *ln; + ngx_http_spdy_stream_t *stream; + + stream = frame->stream; + + cl = frame->first; + + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_data_frame) { + + if (cl->buf->pos != cl->buf->last) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui DATA frame %p was sent partially", + stream->id, frame); + + return NGX_AGAIN; + } + + ln = cl->next; + + cl->next = stream->free_data_headers; + stream->free_data_headers = cl; + + if (cl == frame->last) { + goto done; + } + + cl = ln; + } + + for ( ;; ) { + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) { + buf = cl->buf->shadow; + + if (ngx_buf_in_memory(buf)) { + buf->pos = cl->buf->pos; + } + + if (buf->in_file) { + buf->file_pos = cl->buf->file_pos; + } + } + + if (ngx_buf_size(cl->buf) != 0) { + + if (cl != frame->first) { + frame->first = cl; + ngx_http_spdy_handle_stream(sc, stream); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui DATA frame %p was sent partially", + stream->id, frame); + + return NGX_AGAIN; + } + + ln = cl->next; + + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) { + cl->next = stream->free_bufs; + stream->free_bufs = cl; + + } else { + ngx_free_chain(stream->request->pool, cl); + } + + if (cl == frame->last) { + goto done; + } + + cl = ln; + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui DATA frame %p was sent", stream->id, frame); + + stream->request->header_size += NGX_SPDY_FRAME_HEADER_SIZE; + + ngx_http_spdy_handle_frame(stream, frame); + + ngx_http_spdy_handle_stream(sc, stream); + + return NGX_OK; +} + + +static ngx_inline void +ngx_http_spdy_handle_frame(ngx_http_spdy_stream_t *stream, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_http_request_t *r; + + r = stream->request; + + r->connection->sent += NGX_SPDY_FRAME_HEADER_SIZE + frame->length; + + if (frame->fin) { + stream->out_closed = 1; + } + + frame->next = stream->free_frames; + stream->free_frames = frame; + + stream->queued--; +} + + +static ngx_inline void +ngx_http_spdy_handle_stream(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream) +{ + ngx_event_t *wev; + + if (stream->handled || stream->blocked || stream->exhausted) { + return; + } + + wev = stream->request->connection->write; + + /* + * This timer can only be set if the stream was delayed because of rate + * limit. In that case the event should be triggered by the timer. + */ + + if (!wev->timer_set) { + wev->delayed = 0; + + stream->handled = 1; + ngx_queue_insert_tail(&sc->posted, &stream->queue); + } +} + + +static void +ngx_http_spdy_filter_cleanup(void *data) +{ + ngx_http_spdy_stream_t *stream = data; + + size_t delta; + ngx_http_spdy_out_frame_t *frame, **fn; + ngx_http_spdy_connection_t *sc; + + if (stream->handled) { + stream->handled = 0; + ngx_queue_remove(&stream->queue); + } + + if (stream->queued == 0) { + return; + } + + delta = 0; + sc = stream->connection; + fn = &sc->last_out; + + for ( ;; ) { + frame = *fn; + + if (frame == NULL) { + break; + } + + if (frame->stream == stream && !frame->blocked) { + *fn = frame->next; + + delta += frame->length; + + if (--stream->queued == 0) { + break; + } + + continue; + } + + fn = &frame->next; + } + + if (sc->send_window == 0 && delta && !ngx_queue_empty(&sc->waiting)) { + ngx_queue_add(&sc->posted, &sc->waiting); + ngx_queue_init(&sc->waiting); + } + + sc->send_window += delta; +} + + +static ngx_int_t +ngx_http_spdy_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_spdy_header_filter; + + return NGX_OK; +} diff -uNr a/src/http/ngx_http_spdy.h b/src/http/ngx_http_spdy.h --- a/src/http/ngx_http_spdy.h 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/ngx_http_spdy.h 2020-01-03 11:57:09.000000000 +0800 @@ -0,0 +1,261 @@ +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#ifndef _NGX_HTTP_SPDY_H_INCLUDED_ +#define _NGX_HTTP_SPDY_H_INCLUDED_ + + +#include +#include +#include + +#include + + +#define NGX_SPDY_VERSION 3 + +#define NGX_SPDY_NPN_ADVERTISE "\x08spdy/3.1" +#define NGX_SPDY_NPN_NEGOTIATED "spdy/3.1" + +#define NGX_SPDY_STATE_BUFFER_SIZE 16 + +#define NGX_SPDY_CTL_BIT 1 + +#define NGX_SPDY_SYN_STREAM 1 +#define NGX_SPDY_SYN_REPLY 2 +#define NGX_SPDY_RST_STREAM 3 +#define NGX_SPDY_SETTINGS 4 +#define NGX_SPDY_PING 6 +#define NGX_SPDY_GOAWAY 7 +#define NGX_SPDY_HEADERS 8 +#define NGX_SPDY_WINDOW_UPDATE 9 + +#define NGX_SPDY_FRAME_HEADER_SIZE 8 + +#define NGX_SPDY_SID_SIZE 4 +#define NGX_SPDY_DELTA_SIZE 4 + +#define NGX_SPDY_SYN_STREAM_SIZE 10 +#define NGX_SPDY_SYN_REPLY_SIZE 4 +#define NGX_SPDY_RST_STREAM_SIZE 8 +#define NGX_SPDY_PING_SIZE 4 +#define NGX_SPDY_GOAWAY_SIZE 8 +#define NGX_SPDY_WINDOW_UPDATE_SIZE 8 +#define NGX_SPDY_NV_NUM_SIZE 4 +#define NGX_SPDY_NV_NLEN_SIZE 4 +#define NGX_SPDY_NV_VLEN_SIZE 4 +#define NGX_SPDY_SETTINGS_NUM_SIZE 4 +#define NGX_SPDY_SETTINGS_FID_SIZE 4 +#define NGX_SPDY_SETTINGS_VAL_SIZE 4 + +#define NGX_SPDY_SETTINGS_PAIR_SIZE \ + (NGX_SPDY_SETTINGS_FID_SIZE + NGX_SPDY_SETTINGS_VAL_SIZE) + +#define NGX_SPDY_HIGHEST_PRIORITY 0 +#define NGX_SPDY_LOWEST_PRIORITY 7 + +#define NGX_SPDY_FLAG_FIN 0x01 +#define NGX_SPDY_FLAG_UNIDIRECTIONAL 0x02 +#define NGX_SPDY_FLAG_CLEAR_SETTINGS 0x01 + +#define NGX_SPDY_MAX_FRAME_SIZE ((1 << 24) - 1) + +#define NGX_SPDY_DATA_DISCARD 1 +#define NGX_SPDY_DATA_ERROR 2 +#define NGX_SPDY_DATA_INTERNAL_ERROR 3 + + +typedef struct ngx_http_spdy_connection_s ngx_http_spdy_connection_t; +typedef struct ngx_http_spdy_out_frame_s ngx_http_spdy_out_frame_t; + + +typedef u_char *(*ngx_http_spdy_handler_pt) (ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); + +struct ngx_http_spdy_connection_s { + ngx_connection_t *connection; + ngx_http_connection_t *http_connection; + + ngx_uint_t processing; + + size_t send_window; + size_t recv_window; + size_t init_window; + + ngx_queue_t waiting; + + u_char buffer[NGX_SPDY_STATE_BUFFER_SIZE]; + size_t buffer_used; + ngx_http_spdy_handler_pt handler; + + z_stream zstream_in; + z_stream zstream_out; + + ngx_pool_t *pool; + + ngx_http_spdy_out_frame_t *free_ctl_frames; + ngx_connection_t *free_fake_connections; + + ngx_http_spdy_stream_t **streams_index; + + ngx_http_spdy_out_frame_t *last_out; + + ngx_queue_t posted; + + ngx_http_spdy_stream_t *stream; + + ngx_uint_t entries; + size_t length; + u_char flags; + + ngx_uint_t last_sid; + + unsigned blocked:1; + unsigned incomplete:1; +}; + + +struct ngx_http_spdy_stream_s { + ngx_uint_t id; + ngx_http_request_t *request; + ngx_http_spdy_connection_t *connection; + ngx_http_spdy_stream_t *index; + + ngx_uint_t header_buffers; + ngx_uint_t queued; + + /* + * A change to SETTINGS_INITIAL_WINDOW_SIZE could cause the + * send_window to become negative, hence it's signed. + */ + ssize_t send_window; + size_t recv_window; + + ngx_http_spdy_out_frame_t *free_frames; + ngx_chain_t *free_data_headers; + ngx_chain_t *free_bufs; + + ngx_queue_t queue; + + unsigned priority:3; + unsigned handled:1; + unsigned blocked:1; + unsigned exhausted:1; + unsigned in_closed:1; + unsigned out_closed:1; + unsigned skip_data:2; +}; + + +struct ngx_http_spdy_out_frame_s { + ngx_http_spdy_out_frame_t *next; + ngx_chain_t *first; + ngx_chain_t *last; + ngx_int_t (*handler)(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame); + + ngx_http_spdy_stream_t *stream; + size_t length; + + ngx_uint_t priority; + unsigned blocked:1; + unsigned fin:1; +}; + + +static ngx_inline void +ngx_http_spdy_queue_frame(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_http_spdy_out_frame_t **out; + + for (out = &sc->last_out; *out; out = &(*out)->next) + { + /* + * NB: higher values represent lower priorities. + */ + if (frame->priority >= (*out)->priority) { + break; + } + } + + frame->next = *out; + *out = frame; +} + + +static ngx_inline void +ngx_http_spdy_queue_blocked_frame(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_http_spdy_out_frame_t **out; + + for (out = &sc->last_out; *out; out = &(*out)->next) + { + if ((*out)->blocked) { + break; + } + } + + frame->next = *out; + *out = frame; +} + + +void ngx_http_spdy_init(ngx_event_t *rev); +void ngx_http_spdy_request_headers_init(void); + +ngx_int_t ngx_http_spdy_read_request_body(ngx_http_request_t *r, + ngx_http_client_body_handler_pt post_handler); + +void ngx_http_spdy_close_stream(ngx_http_spdy_stream_t *stream, ngx_int_t rc); + +ngx_int_t ngx_http_spdy_send_output_queue(ngx_http_spdy_connection_t *sc); + + +#define ngx_spdy_frame_aligned_write_uint16(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) + +#define ngx_spdy_frame_aligned_write_uint32(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_spdy_frame_write_uint16 ngx_spdy_frame_aligned_write_uint16 +#define ngx_spdy_frame_write_uint32 ngx_spdy_frame_aligned_write_uint32 + +#else + +#define ngx_spdy_frame_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_spdy_frame_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + + +#define ngx_spdy_ctl_frame_head(t) \ + ((uint32_t) NGX_SPDY_CTL_BIT << 31 | NGX_SPDY_VERSION << 16 | (t)) + +#define ngx_spdy_frame_write_head(p, t) \ + ngx_spdy_frame_aligned_write_uint32(p, ngx_spdy_ctl_frame_head(t)) + +#define ngx_spdy_frame_write_flags_and_len(p, f, l) \ + ngx_spdy_frame_aligned_write_uint32(p, (f) << 24 | (l)) +#define ngx_spdy_frame_write_flags_and_id(p, f, i) \ + ngx_spdy_frame_aligned_write_uint32(p, (f) << 24 | (i)) + +#define ngx_spdy_frame_write_sid ngx_spdy_frame_aligned_write_uint32 +#define ngx_spdy_frame_write_window ngx_spdy_frame_aligned_write_uint32 + +#endif /* _NGX_HTTP_SPDY_H_INCLUDED_ */ diff -uNr a/src/http/ngx_http_spdy_module.c b/src/http/ngx_http_spdy_module.c --- a/src/http/ngx_http_spdy_module.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/ngx_http_spdy_module.c 2020-01-03 11:59:02.000000000 +0800 @@ -0,0 +1,408 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include + + +static ngx_int_t ngx_http_spdy_add_variables(ngx_conf_t *cf); + +static ngx_int_t ngx_http_spdy_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_spdy_request_priority_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_http_spdy_module_init(ngx_cycle_t *cycle); + +static void *ngx_http_spdy_create_main_conf(ngx_conf_t *cf); +static char *ngx_http_spdy_init_main_conf(ngx_conf_t *cf, void *conf); +static void *ngx_http_spdy_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_spdy_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static void *ngx_http_spdy_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_spdy_merge_loc_conf(ngx_conf_t *cf, void *parent, + void *child); + +static char *ngx_http_spdy_recv_buffer_size(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_http_spdy_pool_size(ngx_conf_t *cf, void *post, void *data); +static char *ngx_http_spdy_streams_index_mask(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_http_spdy_chunk_size(ngx_conf_t *cf, void *post, void *data); + + +static ngx_conf_num_bounds_t ngx_http_spdy_headers_comp_bounds = { + ngx_conf_check_num_bounds, 0, 9 +}; + +static ngx_conf_post_t ngx_http_spdy_recv_buffer_size_post = + { ngx_http_spdy_recv_buffer_size }; +static ngx_conf_post_t ngx_http_spdy_pool_size_post = + { ngx_http_spdy_pool_size }; +static ngx_conf_post_t ngx_http_spdy_streams_index_mask_post = + { ngx_http_spdy_streams_index_mask }; +static ngx_conf_post_t ngx_http_spdy_chunk_size_post = + { ngx_http_spdy_chunk_size }; + + +static ngx_command_t ngx_http_spdy_commands[] = { + + { ngx_string("spdy_recv_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_MAIN_CONF_OFFSET, + offsetof(ngx_http_spdy_main_conf_t, recv_buffer_size), + &ngx_http_spdy_recv_buffer_size_post }, + + { ngx_string("spdy_pool_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, pool_size), + &ngx_http_spdy_pool_size_post }, + + { ngx_string("spdy_max_concurrent_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, concurrent_streams), + NULL }, + + { ngx_string("spdy_streams_index_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, streams_index_mask), + &ngx_http_spdy_streams_index_mask_post }, + + { ngx_string("spdy_recv_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, recv_timeout), + NULL }, + + { ngx_string("spdy_keepalive_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, keepalive_timeout), + NULL }, + + { ngx_string("spdy_headers_comp"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, headers_comp), + &ngx_http_spdy_headers_comp_bounds }, + + { ngx_string("spdy_chunk_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_spdy_loc_conf_t, chunk_size), + &ngx_http_spdy_chunk_size_post }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_spdy_module_ctx = { + ngx_http_spdy_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + ngx_http_spdy_create_main_conf, /* create main configuration */ + ngx_http_spdy_init_main_conf, /* init main configuration */ + + ngx_http_spdy_create_srv_conf, /* create server configuration */ + ngx_http_spdy_merge_srv_conf, /* merge server configuration */ + + ngx_http_spdy_create_loc_conf, /* create location configuration */ + ngx_http_spdy_merge_loc_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_spdy_module = { + NGX_MODULE_V1, + &ngx_http_spdy_module_ctx, /* module context */ + ngx_http_spdy_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + ngx_http_spdy_module_init, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_variable_t ngx_http_spdy_vars[] = { + + { ngx_string("spdy"), NULL, + ngx_http_spdy_variable, 0, 0, 0 }, + + { ngx_string("spdy_request_priority"), NULL, + ngx_http_spdy_request_priority_variable, 0, 0, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +static ngx_int_t +ngx_http_spdy_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_spdy_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->spdy_stream) { + v->len = sizeof("3.1") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "3.1"; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_request_priority_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->spdy_stream) { + v->len = 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + v->data = ngx_pnalloc(r->pool, 1); + if (v->data == NULL) { + return NGX_ERROR; + } + + v->data[0] = '0' + (u_char) r->spdy_stream->priority; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_module_init(ngx_cycle_t *cycle) +{ + ngx_http_spdy_request_headers_init(); + + return NGX_OK; +} + + +static void * +ngx_http_spdy_create_main_conf(ngx_conf_t *cf) +{ + ngx_http_spdy_main_conf_t *smcf; + + smcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_main_conf_t)); + if (smcf == NULL) { + return NULL; + } + + smcf->recv_buffer_size = NGX_CONF_UNSET_SIZE; + + return smcf; +} + + +static char * +ngx_http_spdy_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_http_spdy_main_conf_t *smcf = conf; + + ngx_conf_init_size_value(smcf->recv_buffer_size, 256 * 1024); + + return NGX_CONF_OK; +} + + +static void * +ngx_http_spdy_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_spdy_srv_conf_t *sscf; + + sscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_srv_conf_t)); + if (sscf == NULL) { + return NULL; + } + + sscf->pool_size = NGX_CONF_UNSET_SIZE; + + sscf->concurrent_streams = NGX_CONF_UNSET_UINT; + sscf->streams_index_mask = NGX_CONF_UNSET_UINT; + + sscf->recv_timeout = NGX_CONF_UNSET_MSEC; + sscf->keepalive_timeout = NGX_CONF_UNSET_MSEC; + + sscf->headers_comp = NGX_CONF_UNSET; + + return sscf; +} + + +static char * +ngx_http_spdy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_spdy_srv_conf_t *prev = parent; + ngx_http_spdy_srv_conf_t *conf = child; + + ngx_conf_merge_size_value(conf->pool_size, prev->pool_size, 4096); + + ngx_conf_merge_uint_value(conf->concurrent_streams, + prev->concurrent_streams, 100); + + ngx_conf_merge_uint_value(conf->streams_index_mask, + prev->streams_index_mask, 32 - 1); + + ngx_conf_merge_msec_value(conf->recv_timeout, + prev->recv_timeout, 30000); + ngx_conf_merge_msec_value(conf->keepalive_timeout, + prev->keepalive_timeout, 180000); + + ngx_conf_merge_value(conf->headers_comp, prev->headers_comp, 0); + + return NGX_CONF_OK; +} + + +static void * +ngx_http_spdy_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_spdy_loc_conf_t *slcf; + + slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_loc_conf_t)); + if (slcf == NULL) { + return NULL; + } + + slcf->chunk_size = NGX_CONF_UNSET_SIZE; + + return slcf; +} + + +static char * +ngx_http_spdy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_spdy_loc_conf_t *prev = parent; + ngx_http_spdy_loc_conf_t *conf = child; + + ngx_conf_merge_size_value(conf->chunk_size, prev->chunk_size, 8 * 1024); + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_recv_buffer_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp <= 2 * NGX_SPDY_STATE_BUFFER_SIZE) { + return "value is too small"; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_pool_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp < NGX_MIN_POOL_SIZE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the pool size must be no less than %uz", + NGX_MIN_POOL_SIZE); + return NGX_CONF_ERROR; + } + + if (*sp % NGX_POOL_ALIGNMENT) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the pool size must be a multiple of %uz", + NGX_POOL_ALIGNMENT); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_streams_index_mask(ngx_conf_t *cf, void *post, void *data) +{ + ngx_uint_t *np = data; + + ngx_uint_t mask; + + mask = *np - 1; + + if (*np == 0 || (*np & mask)) { + return "must be a power of two"; + } + + *np = mask; + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_chunk_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the spdy chunk size cannot be zero"); + return NGX_CONF_ERROR; + } + + if (*sp > NGX_SPDY_MAX_FRAME_SIZE) { + *sp = NGX_SPDY_MAX_FRAME_SIZE; + } + + return NGX_CONF_OK; +} diff -uNr a/src/http/ngx_http_spdy_module.h b/src/http/ngx_http_spdy_module.h --- a/src/http/ngx_http_spdy_module.h 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/ngx_http_spdy_module.h 2020-01-03 12:00:23.000000000 +0800 @@ -0,0 +1,41 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#ifndef _NGX_HTTP_SPDY_MODULE_H_INCLUDED_ +#define _NGX_HTTP_SPDY_MODULE_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + size_t recv_buffer_size; + u_char *recv_buffer; +} ngx_http_spdy_main_conf_t; + + +typedef struct { + size_t pool_size; + ngx_uint_t concurrent_streams; + ngx_uint_t streams_index_mask; + ngx_msec_t recv_timeout; + ngx_msec_t keepalive_timeout; + ngx_int_t headers_comp; +} ngx_http_spdy_srv_conf_t; + + +typedef struct { + size_t chunk_size; +} ngx_http_spdy_loc_conf_t; + + +extern ngx_module_t ngx_http_spdy_module; + + +#endif /* _NGX_HTTP_SPDY_MODULE_H_INCLUDED_ */ diff -uNr a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/ngx_http_upstream.c 2020-01-03 12:02:16.000000000 +0800 @@ -525,6 +525,18 @@ return; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + ngx_http_upstream_init_request(r); + return; + } +#endif +#if (NGX_HTTP_V3) + if (r->qstream) { + ngx_http_upstream_init_request(r); + return; + } +#endif if (c->read->timer_set) { ngx_del_timer(c->read); @@ -1347,6 +1359,16 @@ return; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + return; + } +#endif +#if (NGX_HTTP_V3) + if (r->qstream) { + return; + } +#endif #if (NGX_HAVE_KQUEUE) diff -uNr a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c --- a/src/http/v2/ngx_http_v2.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/v2/ngx_http_v2.c 2020-01-02 21:25:20.000000000 +0800 @@ -271,6 +271,8 @@ h2c->frame_size = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + h2c->max_hpack_table_size = NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE; + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); h2c->concurrent_pushes = h2scf->concurrent_pushes; @@ -2093,6 +2095,14 @@ case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING: h2c->table_update = 1; + + if (value > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { + h2c->max_hpack_table_size = NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE; + } else { + h2c->max_hpack_table_size = value; + } + + h2c->indicate_resize = 1; break; default: diff -uNr a/src/http/v2/ngx_http_v2_encode.c b/src/http/v2/ngx_http_v2_encode.c --- a/src/http/v2/ngx_http_v2_encode.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_encode.c 2020-01-02 21:25:20.000000000 +0800 @@ -10,7 +10,7 @@ #include -static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, +u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); @@ -40,7 +40,7 @@ } -static u_char * +u_char * ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) { if (value < prefix) { diff -uNr a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c --- a/src/http/v2/ngx_http_v2_filter_module.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_filter_module.c 2020-01-02 21:25:20.000000000 +0800 @@ -23,10 +23,53 @@ #define ngx_http_v2_literal_size(h) \ (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1) +#define ngx_http_v2_indexed(i) (128 + (i)) +#define ngx_http_v2_inc_indexed(i) (64 + (i)) + +#define NGX_HTTP_V2_ENCODE_RAW 0 +#define NGX_HTTP_V2_ENCODE_HUFF 0x80 + +#define NGX_HTTP_V2_AUTHORITY_INDEX 1 +#define NGX_HTTP_V2_METHOD_GET_INDEX 2 +#define NGX_HTTP_V2_PATH_INDEX 4 + +#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6 +#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7 + +#define NGX_HTTP_V2_STATUS_INDEX 8 +#define NGX_HTTP_V2_STATUS_200_INDEX 8 +#define NGX_HTTP_V2_STATUS_204_INDEX 9 +#define NGX_HTTP_V2_STATUS_206_INDEX 10 +#define NGX_HTTP_V2_STATUS_304_INDEX 11 +#define NGX_HTTP_V2_STATUS_400_INDEX 12 +#define NGX_HTTP_V2_STATUS_404_INDEX 13 +#define NGX_HTTP_V2_STATUS_500_INDEX 14 + +#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 +#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 +#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 +#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 +#define NGX_HTTP_V2_DATE_INDEX 33 +#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 +#define NGX_HTTP_V2_LOCATION_INDEX 46 +#define NGX_HTTP_V2_SERVER_INDEX 54 +#define NGX_HTTP_V2_USER_AGENT_INDEX 58 +#define NGX_HTTP_V2_VARY_INDEX 59 #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 +static const struct { + u_char *name; + u_char const len; +} push_header[] = { + { (u_char*)":authority" , 10 }, + { (u_char*)"accept-encoding" , 15 }, + { (u_char*)"accept-language" , 15 }, + { (u_char*)"user-agent" , 10 } +}; + + typedef struct { ngx_str_t name; u_char index; @@ -155,11 +198,9 @@ #endif static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER); - static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)]; static size_t nginx_ver_build_len = ngx_http_v2_literal_size(NGINX_VER_BUILD); - static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)]; stream = r->stream; @@ -435,7 +476,7 @@ } tmp = ngx_palloc(r->pool, tmp_len); - pos = ngx_pnalloc(r->pool, len); + pos = ngx_pnalloc(r->pool, len + 15 + 1); if (pos == NULL || tmp == NULL) { return NGX_ERROR; @@ -443,11 +484,16 @@ start = pos; - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; + h2c = r->stream->connection; + + if (h2c->indicate_resize) { + *pos = 32; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), + h2c->max_hpack_table_size); + h2c->indicate_resize = 0; +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_table_resize(h2c); +#endif } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, @@ -458,67 +504,28 @@ *pos++ = status; } else { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); - *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3; - pos = ngx_sprintf(pos, "%03ui", r->headers_out.status); + ngx_sprintf(pos + 8, "%O3ui", r->headers_out.status); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)":status", + sizeof(":status") - 1, pos + 8, 3, tmp); } if (r->headers_out.server == NULL) { - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: %s\"", - NGINX_VER); - - } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: %s\"", - NGINX_VER_BUILD); - - } else { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"server: nginx\""); - } - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); - - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - if (nginx_ver[0] == '\0') { - p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER, - sizeof(NGINX_VER) - 1, tmp); - nginx_ver_len = p - nginx_ver; - } - - pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len); + pos = ngx_http_v2_write_header_str("server", NGINX_VER); } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - if (nginx_ver_build[0] == '\0') { - p = ngx_http_v2_write_value(nginx_ver_build, - (u_char *) NGINX_VER_BUILD, - sizeof(NGINX_VER_BUILD) - 1, tmp); - nginx_ver_build_len = p - nginx_ver_build; - } - - pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len); + pos = ngx_http_v2_write_header_str("server", NGINX_VER_BUILD); } else { - pos = ngx_cpymem(pos, nginx, sizeof(nginx)); + pos = ngx_http_v2_write_header_str("server", "nginx"); } } if (r->headers_out.date == NULL) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"date: %V\"", - &ngx_cached_http_time); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); - pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data, - ngx_cached_http_time.len, tmp); + pos = ngx_http_v2_write_header_tbl("date", ngx_cached_http_time); } if (r->headers_out.content_type.len) { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); - if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { @@ -544,64 +551,36 @@ r->headers_out.content_type.data = p - len; } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"content-type: %V\"", - &r->headers_out.content_type); - - pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data, - r->headers_out.content_type.len, tmp); + pos = ngx_http_v2_write_header_tbl("content-type", + r->headers_out.content_type); } if (r->headers_out.content_length == NULL && r->headers_out.content_length_n >= 0) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"content-length: %O\"", - r->headers_out.content_length_n); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX); - - p = pos; - pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n); - *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1); + p = ngx_sprintf(pos + 15, "%O", r->headers_out.content_length_n); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"content-length", + sizeof("content-length") - 1, pos + 15, + p - (pos + 15), tmp); } if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); - - ngx_http_time(pos, r->headers_out.last_modified_time); + ngx_http_time(pos + 14, r->headers_out.last_modified_time); len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"last-modified: %*s\"", - len, pos); - - /* - * Date will always be encoded using huffman in the temporary buffer, - * so it's safe here to use src and dst pointing to the same address. - */ - pos = ngx_http_v2_write_value(pos, pos, len, tmp); + pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"last-modified", + sizeof("last-modified") - 1, pos + 14, + len, tmp); } if (r->headers_out.location && r->headers_out.location->value.len) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"location: %V\"", - &r->headers_out.location->value); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); - pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data, - r->headers_out.location->value.len, tmp); + pos = ngx_http_v2_write_header_tbl("location", r->headers_out.location->value); } #if (NGX_HTTP_GZIP) if (r->gzip_vary) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"vary: Accept-Encoding\""); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); - pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding)); + pos = ngx_http_v2_write_header_str("vary", "Accept-Encoding"); } #endif @@ -624,23 +603,10 @@ continue; } -#if (NGX_DEBUG) - if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { - ngx_strlow(tmp, header[i].key.data, header[i].key.len); - - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 output header: \"%*s: %V\"", - header[i].key.len, tmp, &header[i].value); - } -#endif - - *pos++ = 0; - - pos = ngx_http_v2_write_name(pos, header[i].key.data, - header[i].key.len, tmp); + pos = ngx_http_v2_write_header(h2c, pos, header[i].key.data, + header[i].key.len, header[i].value.data, + header[i].value.len, tmp); - pos = ngx_http_v2_write_value(pos, header[i].value.data, - header[i].value.len, tmp); } fin = r->header_only @@ -998,6 +964,7 @@ for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { len += binary[i].len; + len += push_header[i].len + 1; } pos = ngx_pnalloc(r->pool, len); @@ -1007,12 +974,17 @@ start = pos; - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; - } + h2c = r->stream->connection; + + if (h2c->indicate_resize) { + *pos = 32; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), + h2c->max_hpack_table_size); + h2c->indicate_resize = 0; +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_table_resize(h2c); +#endif + } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":method: GET\""); @@ -1022,8 +994,7 @@ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":path: %V\"", path); - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); - pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp); + pos = ngx_http_v2_write_header_pot(":path", path); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":scheme: %V\"", &r->schema); @@ -1048,11 +1019,15 @@ continue; } + value = &(*h)->value; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \"%V: %V\"", &ph[i].name, &(*h)->value); - pos = ngx_cpymem(pos, binary[i].data, binary[i].len); + pos = ngx_http_v2_write_header(h2c, pos, + push_header[i].name, push_header[i].len, value->data, value->len, + tmp); } frame = ngx_http_v2_create_push_frame(r, start, pos); diff -uNr a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h --- a/src/http/v2/ngx_http_v2.h 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/v2/ngx_http_v2.h 2020-01-02 21:25:20.000000000 +0800 @@ -52,6 +52,14 @@ #define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1) #define NGX_HTTP_V2_DEFAULT_WINDOW 65535 +#define HPACK_ENC_HTABLE_SZ 128 /* better to keep a PoT < 64k */ +#define HPACK_ENC_HTABLE_ENTRIES ((HPACK_ENC_HTABLE_SZ * 100) / 128) +#define HPACK_ENC_DYNAMIC_KEY_TBL_SZ 10 /* 10 is sufficient for most */ +#define HPACK_ENC_MAX_ENTRY 512 /* longest header size to match */ + +#define NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE 4096 +#define NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE 16384 /* < 64k */ + #define NGX_HTTP_V2_DEFAULT_WEIGHT 16 @@ -115,6 +123,46 @@ } ngx_http_v2_hpack_t; +#if (NGX_HTTP_V2_HPACK_ENC) +typedef struct { + uint64_t hash_val; + uint32_t index; + uint16_t pos; + uint16_t klen, vlen; + uint16_t size; + uint16_t next; +} ngx_http_v2_hpack_enc_entry_t; + + +typedef struct { + uint64_t hash_val; + uint32_t index; + uint16_t pos; + uint16_t klen; +} ngx_http_v2_hpack_name_entry_t; + + +typedef struct { + size_t size; /* size as defined in RFC 7541 */ + uint32_t top; /* the last entry */ + uint32_t pos; + uint16_t n_elems; /* number of elements */ + uint16_t base; /* index of the oldest entry */ + uint16_t last; /* index of the newest entry */ + + /* hash table for dynamic entries, instead using a generic hash table, + which would be too slow to process a significant amount of headers, + this table is not determenistic, and might ocasionally fail to insert + a value, at the cost of slightly worse compression, but significantly + faster performance */ + ngx_http_v2_hpack_enc_entry_t htable[HPACK_ENC_HTABLE_SZ]; + ngx_http_v2_hpack_name_entry_t heads[HPACK_ENC_DYNAMIC_KEY_TBL_SZ]; + u_char storage[NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE + + HPACK_ENC_MAX_ENTRY]; +} ngx_http_v2_hpack_enc_t; +#endif + + struct ngx_http_v2_connection_s { ngx_connection_t *connection; ngx_http_connection_t *http_connection; @@ -136,6 +184,8 @@ size_t frame_size; + size_t max_hpack_table_size; + ngx_queue_t waiting; ngx_http_v2_state_t state; @@ -163,6 +213,11 @@ unsigned blocked:1; unsigned goaway:1; unsigned push_disabled:1; + unsigned indicate_resize:1; + +#if (NGX_HTTP_V2_HPACK_ENC) + ngx_http_v2_hpack_enc_t hpack_enc; +#endif }; @@ -206,6 +261,8 @@ ngx_array_t *cookies; + size_t header_limit; + ngx_pool_t *pool; unsigned waiting:1; @@ -418,4 +475,35 @@ u_char *tmp, ngx_uint_t lower); +u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, + u_char *tmp, ngx_uint_t lower); + +u_char * +ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); + +#define ngx_http_v2_write_name(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 1) +#define ngx_http_v2_write_value(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 0) + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, u_char *value, size_t value_len, + u_char *tmp); + +void +ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c); + +#define ngx_http_v2_write_header_str(key, value) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + (u_char *) value, sizeof(value) - 1, tmp); + +#define ngx_http_v2_write_header_tbl(key, val) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + val.data, val.len, tmp); + +#define ngx_http_v2_write_header_pot(key, val) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ + val->data, val->len, tmp); + #endif /* _NGX_HTTP_V2_H_INCLUDED_ */ diff -uNr a/src/http/v2/ngx_http_v2_module.c b/src/http/v2/ngx_http_v2_module.c --- a/src/http/v2/ngx_http_v2_module.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_module.c 2020-01-03 12:04:35.000000000 +0800 @@ -36,8 +36,6 @@ static char *ngx_http_v2_streams_index_mask(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_v2_chunk_size(ngx_conf_t *cf, void *post, void *data); -static char *ngx_http_v2_spdy_deprecated(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); static ngx_conf_post_t ngx_http_v2_recv_buffer_size_post = @@ -152,62 +150,6 @@ 0, NULL }, - { ngx_string("spdy_recv_buffer_size"), - NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_MAIN_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_pool_size"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_max_concurrent_streams"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_streams_index_size"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_recv_timeout"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_keepalive_timeout"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_headers_comp"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_chunk_size"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_LOC_CONF_OFFSET, - 0, - NULL }, - ngx_null_command }; @@ -599,12 +541,3 @@ } -static char * -ngx_http_v2_spdy_deprecated(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "invalid directive \"%V\": ngx_http_spdy_module " - "was superseded by ngx_http_v2_module", &cmd->name); - - return NGX_CONF_OK; -} diff -uNr a/src/http/v2/ngx_http_v2_table.c b/src/http/v2/ngx_http_v2_table.c --- a/src/http/v2/ngx_http_v2_table.c 2019-12-24 23:00:09.000000000 +0800 +++ b/src/http/v2/ngx_http_v2_table.c 2020-01-02 21:25:20.000000000 +0800 @@ -361,3 +361,434 @@ return NGX_OK; } + + +#if (NGX_HTTP_V2_HPACK_ENC) + +static ngx_int_t +hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len); + +static ngx_int_t +hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, + uint8_t *key, size_t key_len); + + +void +ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c) +{ + ngx_http_v2_hpack_enc_entry_t *table; + uint64_t idx; + + table = h2c->hpack_enc.htable; + + while (h2c->hpack_enc.size > h2c->max_hpack_table_size) { + idx = h2c->hpack_enc.base; + h2c->hpack_enc.base = table[idx].next; + h2c->hpack_enc.size -= table[idx].size; + table[idx].hash_val = 0; + h2c->hpack_enc.n_elems--; + } +} + + +/* checks if a header is in the hpack table - if so returns the table entry, + otherwise encodes and inserts into the table and returns 0, + if failed to insert into table, returns -1 */ +static ngx_int_t +ngx_http_v2_table_encode_strings(ngx_http_v2_connection_t *h2c, + size_t key_len, size_t val_len, uint8_t *key, uint8_t *val, + ngx_int_t *header_idx) +{ + uint64_t hash_val, key_hash, idx, lru; + int i; + size_t size = key_len + val_len + 32; + uint8_t *storage = h2c->hpack_enc.storage; + + ngx_http_v2_hpack_enc_entry_t *table; + ngx_http_v2_hpack_name_entry_t *name; + + *header_idx = NGX_ERROR; + /* step 1: compute the hash value of header */ + if (size > HPACK_ENC_MAX_ENTRY || size > h2c->max_hpack_table_size) { + return NGX_ERROR; + } + + key_hash = ngx_murmur_hash2_64(key, key_len, 0x01234); + hash_val = ngx_murmur_hash2_64(val, val_len, key_hash); + + if (hash_val == 0) { + return NGX_ERROR; + } + + /* step 2: check if full header in the table */ + idx = hash_val; + i = -1; + while (idx) { + /* at most 8 locations are checked, but most will be done in 1 or 2 */ + table = &h2c->hpack_enc.htable[idx % HPACK_ENC_HTABLE_SZ]; + if (table->hash_val == hash_val + && table->klen == key_len + && table->vlen == val_len + && ngx_memcmp(key, storage + table->pos, key_len) == 0 + && ngx_memcmp(val, storage + table->pos + key_len, val_len) == 0) + { + return (h2c->hpack_enc.top - table->index) + 61; + } + + if (table->hash_val == 0 && i == -1) { + i = idx % HPACK_ENC_HTABLE_SZ; + break; + } + + idx >>= 8; + } + + /* step 3: check if key is in one of the tables */ + *header_idx = hpack_get_static_index(h2c, key, key_len); + + if (i == -1) { + return NGX_ERROR; + } + + if (*header_idx == NGX_ERROR) { + *header_idx = hpack_get_dynamic_index(h2c, key_hash, key, key_len); + } + + /* step 4: store the new entry */ + table = h2c->hpack_enc.htable; + + if (h2c->hpack_enc.top == 0xffffffff) { + /* just to be on the safe side, avoid overflow */ + ngx_memset(&h2c->hpack_enc, 0, sizeof(ngx_http_v2_hpack_enc_t)); + } + + while ((h2c->hpack_enc.size + size > h2c->max_hpack_table_size) + || h2c->hpack_enc.n_elems == HPACK_ENC_HTABLE_ENTRIES) { + /* make space for the new entry first */ + idx = h2c->hpack_enc.base; + h2c->hpack_enc.base = table[idx].next; + h2c->hpack_enc.size -= table[idx].size; + table[idx].hash_val = 0; + h2c->hpack_enc.n_elems--; + } + + table[i] = (ngx_http_v2_hpack_enc_entry_t){.hash_val = hash_val, + .index = h2c->hpack_enc.top, + .pos = h2c->hpack_enc.pos, + .klen = key_len, + .vlen = val_len, + .size = size, + .next = 0}; + + table[h2c->hpack_enc.last].next = i; + if (h2c->hpack_enc.n_elems == 0) { + h2c->hpack_enc.base = i; + } + + h2c->hpack_enc.last = i; + h2c->hpack_enc.top++; + h2c->hpack_enc.size += size; + h2c->hpack_enc.n_elems++; + + /* update header name lookup */ + if (*header_idx == NGX_ERROR ) { + lru = h2c->hpack_enc.top; + + for (i=0; ihpack_enc.heads[i]; + + if ( name->hash_val == 0 || (name->hash_val == key_hash + && ngx_memcmp(storage + name->pos, key, key_len) == 0) ) + { + name->hash_val = key_hash; + name->pos = h2c->hpack_enc.pos; + name->index = h2c->hpack_enc.top - 1; + break; + } + + if (lru > name->index) { + lru = name->index; + idx = i; + } + } + + if (i == HPACK_ENC_DYNAMIC_KEY_TBL_SZ) { + name = &h2c->hpack_enc.heads[idx]; + name->hash_val = hash_val; + name->pos = h2c->hpack_enc.pos; + name->index = h2c->hpack_enc.top - 1; + } + } + + ngx_memcpy(storage + h2c->hpack_enc.pos, key, key_len); + ngx_memcpy(storage + h2c->hpack_enc.pos + key_len, val, val_len); + + h2c->hpack_enc.pos += size; + if (h2c->hpack_enc.pos > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { + h2c->hpack_enc.pos = 0; + } + + return NGX_OK; +} + + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, + u_char *value, size_t value_len, + u_char *tmp) +{ + ngx_int_t idx, header_idx; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 output header: %*s: %*s", key_len, key, value_len, + value); + + /* attempt to find the value in the dynamic table */ + idx = ngx_http_v2_table_encode_strings(h2c, key_len, value_len, key, value, + &header_idx); + + if (idx > 0) { + /* positive index indicates success */ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Indexed Header Field: %ud", idx); + + *pos = 128; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), idx); + + } else { + + if (header_idx == NGX_ERROR) { /* if key is not present */ + + if (idx == NGX_ERROR) { /* if header was not added */ + *pos++ = 0; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field without" + " Indexing — New Name"); + } else { /* if header was added */ + *pos++ = 64; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field with " + "Incremental Indexing — New Name"); + } + + pos = ngx_http_v2_write_name(pos, key, key_len, tmp); + + } else { /* if key is present */ + + if (idx == NGX_ERROR) { + *pos = 0; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(4), header_idx); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field without" + " Indexing — Indexed Name: %ud", header_idx); + } else { + *pos = 64; + pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(6), header_idx); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack encode: Literal Header Field with " + "Incremental Indexing — Indexed Name: %ud", header_idx); + } + } + + pos = ngx_http_v2_write_value(pos, value, value_len, tmp); + } + + return pos; +} + + +static ngx_int_t +hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, + uint8_t *key, size_t key_len) +{ + ngx_http_v2_hpack_name_entry_t *name; + int i; + + for (i=0; ihpack_enc.heads[i]; + + if (name->hash_val == key_hash + && ngx_memcmp(h2c->hpack_enc.storage + name->pos, key, key_len) == 0) + { + if (name->index >= h2c->hpack_enc.top - h2c->hpack_enc.n_elems) { + return (h2c->hpack_enc.top - name->index) + 61; + } + break; + } + } + + return NGX_ERROR; +} + + +/* decide if a given header is present in the static dictionary, this could be + done in several ways, but it seems the fastest one is "exhaustive" search */ +static ngx_int_t +hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len) +{ + /* the static dictionary of response only headers, + although response headers can be put by origin, + that would be rare */ + static const struct { + u_char len; + const u_char val[28]; + u_char idx; + } server_headers[] = { + { 3, "age", 21},//0 + { 3, "via", 60}, + { 4, "date", 33},//2 + { 4, "etag", 34}, + { 4, "link", 45}, + { 4, "vary", 59}, + { 5, "allow", 22},//6 + { 6, "server", 54},//7 + { 7, "expires", 36},//8 + { 7, "refresh", 52}, + { 8, "location", 46},//10 + {10, "set-cookie", 55},//11 + {11, "retry-after", 53},//12 + {12, "content-type", 31},//13 + {13, "content-range", 30},//14 + {13, "accept-ranges", 18}, + {13, "cache-control", 24}, + {13, "last-modified", 44}, + {14, "content-length", 28},//18 + {16, "content-encoding", 26},//19 + {16, "content-language", 27}, + {16, "content-location", 29}, + {16, "www-authenticate", 61}, + {17, "transfer-encoding", 57},//23 + {18, "proxy-authenticate", 48},//24 + {19, "content-disposition", 25},//25 + {25, "strict-transport-security", 56},//26 + {27, "access-control-allow-origin", 20},//27 + {99, "", 99}, + }, *header; + + /* for a given length, where to start the search + since minimal length is 3, the table has a -3 + offset */ + static const int8_t start_at[] = { + [3-3] = 0, + [4-3] = 2, + [5-3] = 6, + [6-3] = 7, + [7-3] = 8, + [8-3] = 10, + [9-3] = -1, + [10-3] = 11, + [11-3] = 12, + [12-3] = 13, + [13-3] = 14, + [14-3] = 18, + [15-3] = -1, + [16-3] = 19, + [17-3] = 23, + [18-3] = 24, + [19-3] = 25, + [20-3] = -1, + [21-3] = -1, + [22-3] = -1, + [23-3] = -1, + [24-3] = -1, + [25-3] = 26, + [26-3] = -1, + [27-3] = 27, + }; + + uint64_t pref; + size_t save_len = len, i; + int8_t start; + + /* early exit for out of bounds lengths */ + if (len < 3 || len > 27) { + return NGX_ERROR; + } + + start = start_at[len - 3]; + if (start == -1) { + /* exit for non existent lengths */ + return NGX_ERROR; + } + + header = &server_headers[start_at[len - 3]]; + + /* load first 8 bytes of key, for fast comparison */ + if (len < 8) { + pref = 0; + if (len >= 4) { + pref = *(uint32_t *)(val + len - 4) | 0x20202020; + len -= 4; + } + while (len > 0) { /* 3 iterations at most */ + pref = (pref << 8) ^ (val[len - 1] | 0x20); + len--; + } + } else { + pref = *(uint64_t *)val | 0x2020202020202020; + len -= 8; + } + + /* iterate over headers with the right length */ + while (header->len == save_len) { + /* quickly compare the first 8 bytes, most tests will end here */ + if (pref != *(uint64_t *) header->val) { + header++; + continue; + } + + if (len == 0) { + /* len == 0, indicates prefix held the entire key */ + return header->idx; + } + /* for longer keys compare the rest */ + i = 1 + (save_len + 7) % 8; /* align so we can compare in quadwords */ + + while (i + 8 <= save_len) { /* 3 iterations at most */ + if ( *(uint64_t *)&header->val[i] + != (*(uint64_t *) &val[i]| 0x2020202020202020) ) + { + header++; + i = 0; + break; + } + i += 8; + } + + if (i == 0) { + continue; + } + + /* found the corresponding entry in the static dictionary */ + return header->idx; + } + + return NGX_ERROR; +} + +#else + +u_char * +ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *key, size_t key_len, + u_char *value, size_t value_len, + u_char *tmp) +{ + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 output header: %*s: %*s", key_len, key, value_len, + value); + + *pos++ = 64; + pos = ngx_http_v2_write_name(pos, key, key_len, tmp); + pos = ngx_http_v2_write_value(pos, value, value_len, tmp); + + return pos; +} + +#endif diff -uNr a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c --- a/src/http/v3/ngx_http_v3.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3.c 2020-01-02 21:25:20.000000000 +0800 @@ -0,0 +1,2092 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#include +#include +#include +#include + + +typedef struct { + ngx_str_t name; + ngx_uint_t offset; + ngx_uint_t hash; + ngx_http_header_t *hh; +} ngx_http_v3_parse_header_t; + + +/* errors */ +#define NGX_HTTP_V3_NO_ERROR 0x0 +#define NGX_HTTP_V3_INTERNAL_ERROR 0x3 + + +static void ngx_http_v3_handler(ngx_connection_t *c); + +static ngx_http_v3_stream_t *ngx_http_v3_stream_lookup( + ngx_http_v3_connection_t *h3c, ngx_uint_t stream_id); +static ngx_http_v3_stream_t *ngx_http_v3_create_stream( + ngx_http_v3_connection_t *h3c); +static void ngx_http_v3_close_stream_handler(ngx_event_t *ev); + +static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r, + ngx_http_v3_header_t *header); +static ngx_int_t ngx_http_v3_pseudo_header(ngx_http_request_t *r, + ngx_http_v3_header_t *header); +static ngx_int_t ngx_http_v3_parse_path(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v3_parse_method(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v3_parse_scheme(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v3_parse_authority(ngx_http_request_t *r, + ngx_str_t *value); +static ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, + ngx_http_v3_parse_header_t *header, ngx_str_t *value); +static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r, + ngx_http_v3_header_t *header); +static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_construct_request_line(ngx_http_request_t *r); + +static void ngx_http_v3_run_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_process_request_body(ngx_http_request_t *r, + ngx_uint_t do_read, ngx_uint_t last); +static ngx_int_t ngx_http_v3_filter_request_body(ngx_http_request_t *r); +static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r); + +static ngx_chain_t *ngx_http_v3_send_chain(ngx_connection_t *fc, + ngx_chain_t *in, off_t limit); + +static void ngx_http_v3_finalize_connection(ngx_http_v3_connection_t *h3c, + ngx_uint_t status); + +static void ngx_http_v3_pool_cleanup(void *data); + + +static ngx_http_v3_parse_header_t ngx_http_v3_parse_headers[] = { + { ngx_string("host"), + offsetof(ngx_http_headers_in_t, host), 0, NULL }, + + { ngx_string("accept-encoding"), + offsetof(ngx_http_headers_in_t, accept_encoding), 0, NULL }, + + { ngx_string("accept-language"), + offsetof(ngx_http_headers_in_t, accept_language), 0, NULL }, + + { ngx_string("user-agent"), + offsetof(ngx_http_headers_in_t, user_agent), 0, NULL }, + + { ngx_null_string, 0, 0, NULL } +}; + + +void +ngx_http_v3_init(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_connection_t *h3c; + + c = rev->data; + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "init http3 connection"); + + c->log->action = "processing HTTP/3 connection"; + + h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t)); + if (h3c == NULL) { + ngx_http_close_connection(c); + return; + } + + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + + h3c->h3 = quiche_h3_conn_new_with_transport(c->quic->conn, h3scf->http3); + if (h3c->h3 == NULL) { + ngx_http_close_connection(c); + return; + } + + h3c->http_connection = hc; + + h3c->connection = c; + + h3c->pool = c->pool; + + c->data = h3c; + + c->quic->handler = ngx_http_v3_handler; + + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_v3_pool_cleanup; + cln->data = h3c; + + ngx_rbtree_init(&h3c->streams, &h3c->streams_sentinel, + ngx_rbtree_insert_value); +} + + +static int +ngx_http_v3_for_each_header(uint8_t *name, size_t name_len, + uint8_t *value, size_t value_len, void *argp) +{ + ngx_int_t rc; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_request_t *r; + ngx_http_v3_header_t header; + ngx_http_core_srv_conf_t *cscf; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + r = argp; + + /* Duplicate the header name because we don't own it. */ + header.name.data = ngx_pnalloc(r->pool, name_len); + if (header.name.data == NULL) { + return NGX_ERROR; + } + header.name.len = name_len; + + ngx_memcpy(header.name.data, name, name_len); + + /* Duplicate the header value because we don't own it. Some of the + * functions that process headers require a NULL-terminated string, + * so allocate enough memory for that. */ + header.value.data = ngx_pcalloc(r->pool, value_len + 1); + if (header.value.data == NULL) { + return NGX_ERROR; + } + header.value.len = value_len; + + ngx_memcpy(header.value.data, value, value_len); + + if (ngx_http_v3_validate_header(r, &header) != NGX_OK) { + return NGX_ERROR; + } + + /* Check for pseudo-header. */ + if (header.name.data[0] == ':') { + rc = ngx_http_v3_pseudo_header(r, &header); + + if (rc == NGX_OK) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \":%V: %V\"", + &header.name, &header.value); + + return NGX_OK; + } + + return NGX_ERROR; + } + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%V\"", &header.name); + + return NGX_ERROR; + } + } + + /* Handle Cookie header separately. Not sure why, but the HTTP/2 code does + * the same. */ + if (header.name.len == cookie.len + && ngx_memcmp(header.name.data, cookie.data, cookie.len) == 0) + { + if (ngx_http_v3_cookie(r, &header) != NGX_OK) { + return NGX_ERROR; + } + + } else { + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key.len = header.name.len; + h->key.data = header.name.data; + + /* + * TODO Optimization: precalculate hash + * and handler for indexed headers. + */ + h->hash = ngx_hash_key(h->key.data, h->key.len); + + h->value.len = header.value.len; + h->value.data = header.value.data; + + h->lowcase_key = h->key.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \"%V: %V\"", + &header.name, &header.value); + + return NGX_OK; +} + + +static void +ngx_http_v3_process_headers(ngx_connection_t *c, quiche_h3_event *ev, + int64_t stream_id) +{ + int rc; + ngx_http_v3_stream_t *stream; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_connection_t *h3c; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 process headers"); + + h3c = c->data; + + h3scf = ngx_http_get_module_srv_conf(h3c->http_connection->conf_ctx, + ngx_http_v3_module); + + if (h3c->connection->requests >= h3scf->max_requests) { + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_NO_ERROR); + return; + } + + /* Create a new stream to handle the incoming request. */ + stream = ngx_http_v3_create_stream(h3c); + if (stream == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create HTTP/3 stream"); + + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_INTERNAL_ERROR); + return; + } + + stream->id = stream_id; + + stream->node.key = stream_id; + + ngx_rbtree_insert(&h3c->streams, &stream->node); + + /* Populate ngx_http_request_t from raw HTTP/3 headers. */ + rc = quiche_h3_event_for_each_header(ev, + ngx_http_v3_for_each_header, stream->request); + + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "received invalid HTTP/3 headers"); + + ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_INTERNAL_ERROR); + return; + } + + stream->in_closed = !quiche_h3_event_headers_has_body(ev); + + ngx_http_v3_run_request(stream->request); +} + + +static ngx_int_t +ngx_http_v3_process_data(ngx_connection_t *c, int64_t stream_id) +{ + int rc; + ngx_http_request_t *r; + ngx_http_v3_stream_t *stream; + ngx_http_v3_connection_t *h3c; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 process data"); + + h3c = c->data; + + stream = ngx_http_v3_stream_lookup(h3c, stream_id); + + if (stream == NULL) { + + return NGX_OK; + } + + if (stream->skip_data) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "skipping http3 DATA frame"); + + return NGX_OK; + } + + r = stream->request; + + if (!r->request_body) { + return NGX_AGAIN; + } + + rc = ngx_http_v3_process_request_body(r, 1, stream->in_closed); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc != NGX_OK) { + stream->skip_data = 1; + ngx_http_finalize_request(r, rc); + } + + return NGX_OK; +} + + +static void +ngx_http_v3_process_blocked_streams(ngx_http_v3_connection_t *h3c) +{ + ngx_event_t *wev; + quiche_stream_iter *writable; + ngx_http_v3_stream_t *stream; + uint64_t stream_id; + + writable = quiche_conn_writable(h3c->connection->quic->conn); + + while (quiche_stream_iter_next(writable, &stream_id)) { + stream = ngx_http_v3_stream_lookup(h3c, stream_id); + + if (stream == NULL) { + continue; + } + + if (!stream->blocked) { + continue; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 stream unblocked %ui", stream->id); + + stream->blocked = 0; + + wev = stream->request->connection->write; + + wev->active = 0; + wev->ready = 1; + + if (!wev->delayed) { + wev->handler(wev); + } + } + + quiche_stream_iter_free(writable); +} + + +static void +ngx_http_v3_handler(ngx_connection_t *c) +{ + ngx_http_v3_connection_t *h3c; + ngx_http_v3_stream_t *stream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 handler"); + + h3c = c->data; + + if (c->error) { + ngx_http_v3_finalize_connection(h3c, 0); + return; + } + + ngx_http_v3_process_blocked_streams(h3c); + + while (!c->error) { + quiche_h3_event *ev; + + int64_t stream_id = quiche_h3_conn_poll(h3c->h3, c->quic->conn, &ev); + if (stream_id < 0) { + break; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 event stream:%ui ev:%ui", stream_id, + quiche_h3_event_type(ev)); + + switch (quiche_h3_event_type(ev)) { + case QUICHE_H3_EVENT_HEADERS: { + ngx_http_v3_process_headers(c, ev, stream_id); + break; + } + + case QUICHE_H3_EVENT_DATA: { + if (ngx_http_v3_process_data(c, stream_id) == NGX_AGAIN) { + quiche_h3_event_free(ev); + return; + } + + break; + } + + case QUICHE_H3_EVENT_FINISHED: { + /* Lookup stream. If there isn't one, it means it has already + * been closed, so ignore the event. */ + stream = ngx_http_v3_stream_lookup(h3c, stream_id); + + if (stream != NULL && !stream->in_closed) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 finished"); + + stream->in_closed = 1; + + /* Flush request body that was buffered. */ + if (stream->request->request_body) { + ngx_http_v3_process_request_body(stream->request, 0, 1); + } + } + + break; + } + } + + quiche_h3_event_free(ev); + } +} + + +static ngx_http_v3_stream_t * +ngx_http_v3_create_stream(ngx_http_v3_connection_t *h3c) +{ + ngx_log_t *log; + ngx_event_t *rev, *wev; + ngx_connection_t *fc; + ngx_http_log_ctx_t *ctx; + ngx_http_request_t *r; + ngx_http_v3_stream_t *stream; + ngx_http_core_srv_conf_t *cscf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 create stream"); + + fc = h3c->free_fake_connections; + + if (fc) { + h3c->free_fake_connections = fc->data; + + rev = fc->read; + wev = fc->write; + log = fc->log; + ctx = log->data; + + } else { + fc = ngx_palloc(h3c->pool, sizeof(ngx_connection_t)); + if (fc == NULL) { + return NULL; + } + + rev = ngx_palloc(h3c->pool, sizeof(ngx_event_t)); + if (rev == NULL) { + return NULL; + } + + wev = ngx_palloc(h3c->pool, sizeof(ngx_event_t)); + if (wev == NULL) { + return NULL; + } + + log = ngx_palloc(h3c->pool, sizeof(ngx_log_t)); + if (log == NULL) { + return NULL; + } + + ctx = ngx_palloc(h3c->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + return NULL; + } + + ctx->connection = fc; + ctx->request = NULL; + ctx->current_request = NULL; + } + + ngx_memcpy(log, h3c->connection->log, sizeof(ngx_log_t)); + + log->data = ctx; + + ngx_memzero(rev, sizeof(ngx_event_t)); + + rev->data = fc; + rev->ready = 1; + rev->handler = ngx_http_v3_close_stream_handler; + rev->log = log; + + ngx_memcpy(wev, rev, sizeof(ngx_event_t)); + + wev->write = 1; + + ngx_memcpy(fc, h3c->connection, sizeof(ngx_connection_t)); + + fc->data = h3c->http_connection; + fc->quic = h3c->connection->quic; + fc->read = rev; + fc->write = wev; + fc->sent = 0; + fc->buffer = NULL; + fc->log = log; + fc->buffered = 0; + fc->sndlowat = 1; + fc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; + + fc->send_chain = ngx_http_v3_send_chain; + fc->need_last_buf = 1; + + r = ngx_http_create_request(fc); + if (r == NULL) { + return NULL; + } + + ngx_str_set(&r->http_protocol, "HTTP/3"); + + r->http_version = NGX_HTTP_VERSION_3; + r->valid_location = 1; + + fc->data = r; + h3c->connection->requests++; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_in = ngx_create_temp_buf(r->pool, + cscf->client_header_buffer_size); + if (r->header_in == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; + + stream = ngx_pcalloc(h3c->pool, sizeof(ngx_http_v3_stream_t)); + if (stream == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->qstream = stream; + + stream->request = r; + stream->connection = h3c; + + h3c->processing++; + + return stream; +} + + +static ngx_http_v3_stream_t * +ngx_http_v3_stream_lookup(ngx_http_v3_connection_t *h3c, ngx_uint_t stream_id) +{ + ngx_rbtree_node_t *node, *sentinel; + + node = h3c->streams.root; + sentinel = h3c->streams.sentinel; + + while (node != sentinel) { + + if (stream_id < node->key) { + node = node->left; + continue; + } + + if (stream_id > node->key) { + node = node->right; + continue; + } + + /* stream_id == node->key */ + + return (ngx_http_v3_stream_t *) node; + } + + /* not found */ + + return NULL; +} + + +/* The following functions are copied from the HTTP/2 module, and adapted to + * work independently. In theory we could refactor the HTTP/2 module to expose + * these functions, but that would be fairly invasive and likely cause more + * merge conflicts in the future. */ + + +static ngx_int_t +ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_http_v3_header_t *header) +{ + u_char ch; + ngx_uint_t i; + ngx_http_core_srv_conf_t *cscf; + + if (header->name.len == 0) { + return NGX_ERROR; + } + + r->invalid_header = 0; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + for (i = (header->name.data[0] == ':'); i != header->name.len; i++) { + ch = header->name.data[i]; + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + if (ch == '\0' || ch == LF || ch == CR || ch == ':' + || (ch >= 'A' && ch <= 'Z')) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", + &header->name); + + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + for (i = 0; i != header->value.len; i++) { + ch = header->value.data[i]; + + if (ch == '\0' || ch == LF || ch == CR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "invalid value: \"%V\"", + &header->name, &header->value); + + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_pseudo_header(ngx_http_request_t *r, ngx_http_v3_header_t *header) +{ + header->name.len--; + header->name.data++; + + switch (header->name.len) { + case 4: + if (ngx_memcmp(header->name.data, "path", sizeof("path") - 1) + == 0) + { + return ngx_http_v3_parse_path(r, &header->value); + } + + break; + + case 6: + if (ngx_memcmp(header->name.data, "method", sizeof("method") - 1) + == 0) + { + return ngx_http_v3_parse_method(r, &header->value); + } + + if (ngx_memcmp(header->name.data, "scheme", sizeof("scheme") - 1) + == 0) + { + return ngx_http_v3_parse_scheme(r, &header->value); + } + + break; + + case 9: + if (ngx_memcmp(header->name.data, "authority", sizeof("authority") - 1) + == 0) + { + return ngx_http_v3_parse_authority(r, &header->value); + } + + break; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent unknown pseudo-header \":%V\"", + &header->name); + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_http_v3_parse_path(ngx_http_request_t *r, ngx_str_t *value) +{ + if (r->unparsed_uri.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :path header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :path header"); + + return NGX_DECLINED; + } + + r->uri_start = value->data; + r->uri_end = value->data + value->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid :path header: \"%V\"", value); + + return NGX_DECLINED; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_request_uri() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_parse_method(ngx_http_request_t *r, ngx_str_t *value) +{ + size_t k, len; + ngx_uint_t n; + const u_char *p, *m; + + /* + * This array takes less than 256 sequential bytes, + * and if typical CPU cache line size is 64 bytes, + * it is prefetched for 4 load operations. + */ + static const struct { + u_char len; + const u_char method[11]; + uint32_t value; + } tests[] = { + { 3, "GET", NGX_HTTP_GET }, + { 4, "POST", NGX_HTTP_POST }, + { 4, "HEAD", NGX_HTTP_HEAD }, + { 7, "OPTIONS", NGX_HTTP_OPTIONS }, + { 8, "PROPFIND", NGX_HTTP_PROPFIND }, + { 3, "PUT", NGX_HTTP_PUT }, + { 5, "MKCOL", NGX_HTTP_MKCOL }, + { 6, "DELETE", NGX_HTTP_DELETE }, + { 4, "COPY", NGX_HTTP_COPY }, + { 4, "MOVE", NGX_HTTP_MOVE }, + { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, + { 4, "LOCK", NGX_HTTP_LOCK }, + { 6, "UNLOCK", NGX_HTTP_UNLOCK }, + { 5, "PATCH", NGX_HTTP_PATCH }, + { 5, "TRACE", NGX_HTTP_TRACE } + }, *test; + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :method header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :method header"); + + return NGX_DECLINED; + } + + r->method_name.len = value->len; + r->method_name.data = value->data; + + len = r->method_name.len; + n = sizeof(tests) / sizeof(tests[0]); + test = tests; + + do { + if (len == test->len) { + p = r->method_name.data; + m = test->method; + k = len; + + do { + if (*p++ != *m++) { + goto next; + } + } while (--k); + + r->method = test->value; + return NGX_OK; + } + + next: + test++; + + } while (--n); + + p = r->method_name.data; + + do { + if ((*p < 'A' || *p > 'Z') && *p != '_' && *p != '-') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", + &r->method_name); + + return NGX_DECLINED; + } + + p++; + + } while (--len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_parse_scheme(ngx_http_request_t *r, ngx_str_t *value) +{ + if (r->schema_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :scheme header"); + + return NGX_DECLINED; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :scheme header"); + + return NGX_DECLINED; + } + + r->schema_start = value->data; + r->schema_end = value->data + value->len; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_parse_authority(ngx_http_request_t *r, ngx_str_t *value) +{ + return ngx_http_v3_parse_header(r, &ngx_http_v3_parse_headers[0], value); +} + + +static ngx_int_t +ngx_http_v3_parse_header(ngx_http_request_t *r, + ngx_http_v3_parse_header_t *header, ngx_str_t *value) +{ + ngx_table_elt_t *h; + ngx_http_core_main_conf_t *cmcf; + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key.len = header->name.len; + h->key.data = header->name.data; + h->lowcase_key = header->name.data; + + if (header->hh == NULL) { + header->hash = ngx_hash_key(header->name.data, header->name.len); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + header->hh = ngx_hash_find(&cmcf->headers_in_hash, header->hash, + h->lowcase_key, h->key.len); + if (header->hh == NULL) { + return NGX_ERROR; + } + } + + h->hash = header->hash; + + h->value.len = value->len; + h->value.data = value->data; + + if (header->hh->handler(r, h, header->hh->offset) != NGX_OK) { + /* header handler has already finalized request */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_construct_request_line(ngx_http_request_t *r) +{ + u_char *p; + + static const u_char ending[] = " HTTP/3"; + + if (r->method_name.len == 0 + || r->schema_start == NULL + || r->unparsed_uri.len == 0) + { + if (r->method_name.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :method header"); + + } else if (r->schema_start == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :scheme header"); + + } else { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no :path header"); + } + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + r->request_line.len = r->method_name.len + 1 + + r->unparsed_uri.len + + sizeof(ending) - 1; + + p = ngx_pnalloc(r->pool, r->request_line.len + 1); + if (p == NULL) { + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + + *p++ = ' '; + + p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len); + + ngx_memcpy(p, ending, sizeof(ending)); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request line: \"%V\"", &r->request_line); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_cookie(ngx_http_request_t *r, ngx_http_v3_header_t *header) +{ + ngx_str_t *val; + ngx_array_t *cookies; + + cookies = r->qstream->cookies; + + if (cookies == NULL) { + cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t)); + if (cookies == NULL) { + return NGX_ERROR; + } + + r->qstream->cookies = cookies; + } + + val = ngx_array_push(cookies); + if (val == NULL) { + return NGX_ERROR; + } + + val->len = header->value.len; + val->data = header->value.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_construct_cookie_header(ngx_http_request_t *r) +{ + u_char *buf, *p, *end; + size_t len; + ngx_str_t *vals; + ngx_uint_t i; + ngx_array_t *cookies; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + cookies = r->qstream->cookies; + + if (cookies == NULL) { + return NGX_OK; + } + + vals = cookies->elts; + + i = 0; + len = 0; + + do { + len += vals[i].len + 2; + } while (++i != cookies->nelts); + + len -= 2; + + buf = ngx_pnalloc(r->pool, len + 1); + if (buf == NULL) { + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + p = buf; + end = buf + len; + + for (i = 0; /* void */ ; i++) { + + p = ngx_cpymem(p, vals[i].data, vals[i].len); + + if (p == end) { + *p = '\0'; + break; + } + + *p++ = ';'; *p++ = ' '; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( + ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e'); + + h->key.len = cookie.len; + h->key.data = cookie.data; + + h->value.len = len; + h->value.data = buf; + + h->lowcase_key = cookie.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_multi_header_lines() + */ + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_http_v3_run_request(ngx_http_request_t *r) +{ + if (ngx_http_v3_construct_request_line(r) != NGX_OK) { + return; + } + + if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { + return; + } + + r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; + + if (ngx_http_process_request_header(r) != NGX_OK) { + return; + } + + if (r->headers_in.content_length_n > 0 && r->qstream->in_closed) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + + r->qstream->skip_data = 1; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return; + } + + if (r->headers_in.content_length_n == -1 && !r->qstream->in_closed) { + r->headers_in.chunked = 1; + } + + ngx_http_process_request(r); +} + + +ngx_int_t +ngx_http_v3_read_request_body(ngx_http_request_t *r) +{ + off_t len; + ngx_http_v3_stream_t *stream; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 read request body"); + + stream = r->qstream; + rb = r->request_body; + + if (stream->skip_data) { + r->request_body_no_buffering = 0; + rb->post_handler(r); + return NGX_OK; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + len = r->headers_in.content_length_n; + + if (r->request_body_no_buffering && !stream->in_closed) { + + if (len < 0 || len > (off_t) clcf->client_body_buffer_size) { + len = clcf->client_body_buffer_size; + } + + rb->buf = ngx_create_temp_buf(r->pool, (size_t) len); + + } else if (len >= 0 && len <= (off_t) clcf->client_body_buffer_size + && !r->request_body_in_file_only) + { + rb->buf = ngx_create_temp_buf(r->pool, (size_t) len); + + } else { + rb->buf = ngx_calloc_buf(r->pool); + + if (rb->buf != NULL) { + rb->buf->sync = 1; + } + } + + if (rb->buf == NULL) { + stream->skip_data = 1; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + rb->rest = 1; + + if (stream->in_closed) { + r->request_body_no_buffering = 0; + + return ngx_http_v3_process_request_body(r, 0, 1); + } + + /* TODO: set timer */ + ngx_add_timer(r->connection->read, clcf->client_body_timeout); + + r->read_event_handler = ngx_http_v3_read_client_request_body_handler; + r->write_event_handler = ngx_http_request_empty_handler; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_v3_process_request_body(ngx_http_request_t *r, ngx_uint_t do_read, + ngx_uint_t last) +{ + ssize_t len = 0; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_connection_t *c, *fc; + ngx_http_v3_connection_t *h3c; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + fc = r->connection; + h3c = r->qstream->connection; + c = h3c->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 process request body"); + + rb = r->request_body; + buf = rb->buf; + + if (buf->sync) { + buf->pos = buf->start; + buf->last = buf->start; + + r->request_body_in_file_only = 1; + } + + if (do_read) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 reading %z bytes of request body", + buf->end - buf->last); + + if (buf->last == buf->end) { + return NGX_AGAIN; + } + + len = quiche_h3_recv_body(h3c->h3, c->quic->conn, r->qstream->id, + buf->last, buf->end - buf->last); + + if (len == QUICHE_ERR_DONE) { + return NGX_AGAIN; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 read %z bytes of request body", len); + + buf->last += len; + } + + if (last) { + rb->rest = 0; + + if (fc->read->timer_set) { + ngx_del_timer(fc->read); + } + + if (r->request_body_no_buffering) { + ngx_post_event(fc->read, &ngx_posted_events); + return NGX_OK; + } + + rc = ngx_http_v3_filter_request_body(r); + + if (rc != NGX_OK) { + return rc; + } + + if (buf->sync) { + /* prevent reusing this buffer in the upstream module */ + rb->buf = NULL; + } + + if (r->headers_in.chunked) { + r->headers_in.content_length_n = rb->received; + } + + r->read_event_handler = ngx_http_block_reading; + rb->post_handler(r); + + return NGX_OK; + } + + if (len == 0) { + return NGX_OK; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + ngx_add_timer(fc->read, clcf->client_body_timeout); + + if (r->request_body_no_buffering) { + ngx_post_event(fc->read, &ngx_posted_events); + return NGX_AGAIN; + } + + if (buf->sync) { + return ngx_http_v3_filter_request_body(r); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_filter_request_body(ngx_http_request_t *r) +{ + ngx_buf_t *b, *buf; + ngx_int_t rc; + ngx_chain_t *cl; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 filter request body"); + + rb = r->request_body; + buf = rb->buf; + + if (buf->pos == buf->last && rb->rest) { + cl = NULL; + goto update; + } + + cl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (cl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b = cl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + if (buf->pos != buf->last) { + r->request_length += buf->last - buf->pos; + rb->received += buf->last - buf->pos; + + if (r->headers_in.content_length_n != -1) { + if (rb->received > r->headers_in.content_length_n) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client intended to send body data " + "larger than declared"); + + return NGX_HTTP_BAD_REQUEST; + } + + } else { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->client_max_body_size + && rb->received > clcf->client_max_body_size) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send too large chunked body: " + "%O bytes", rb->received); + + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } + } + + b->temporary = 1; + b->pos = buf->pos; + b->last = buf->last; + b->start = b->pos; + b->end = b->last; + + buf->pos = buf->last; + } + + if (!rb->rest) { + if (r->headers_in.content_length_n != -1 + && r->headers_in.content_length_n != rb->received) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream: " + "only %O out of %O bytes of request body received", + rb->received, r->headers_in.content_length_n); + + return NGX_HTTP_BAD_REQUEST; + } + + b->last_buf = 1; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_request_body; + b->flush = r->request_body_no_buffering; + +update: + + rc = ngx_http_top_request_body_filter(r, cl); + + ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &cl, + (ngx_buf_tag_t) &ngx_http_v3_filter_request_body); + + return rc; +} + + +static void +ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r) +{ + ngx_connection_t *fc; + + fc = r->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 read client request body handler"); + + if (fc->read->timedout) { + ngx_log_error(NGX_LOG_INFO, fc->log, NGX_ETIMEDOUT, "client timed out"); + + fc->timedout = 1; + r->qstream->skip_data = 1; + + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + if (fc->error) { + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client prematurely closed stream"); + + r->qstream->skip_data = 1; + + ngx_http_finalize_request(r, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } +} + + +ngx_int_t +ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r) +{ + ngx_buf_t *buf; + ngx_int_t rc; + ngx_connection_t *fc; + ngx_http_v3_stream_t *stream; + + stream = r->qstream; + fc = r->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 read unbuffered request body"); + + if (fc->read->timedout) { + stream->skip_data = 1; + fc->timedout = 1; + + return NGX_HTTP_REQUEST_TIME_OUT; + } + + if (fc->error) { + stream->skip_data = 1; + return NGX_HTTP_BAD_REQUEST; + } + + rc = ngx_http_v3_filter_request_body(r); + + if (rc != NGX_OK) { + stream->skip_data = 1; + return rc; + } + + if (!r->request_body->rest) { + return NGX_OK; + } + + if (r->request_body->busy != NULL) { + return NGX_AGAIN; + } + + buf = r->request_body->buf; + + buf->pos = buf->start; + buf->last = buf->start; + + ngx_post_event(stream->connection->connection->read, &ngx_posted_events); + + return NGX_AGAIN; +} + + +/* End of functions copied from HTTP/2 module. */ + + +ngx_int_t +ngx_http_v3_send_response(ngx_http_request_t *r) +{ + u_char *tmp; + u_char status[3], content_len[NGX_OFF_T_LEN], + last_modified[sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1], + addr[NGX_SOCKADDR_STRLEN]; + size_t len; + ngx_array_t *headers; + ngx_str_t host, location; + ngx_uint_t i, port, fin; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_connection_t *c, *fc; + quiche_h3_header *h; + ngx_http_v3_connection_t *h3c; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 send response"); + + fc = r->connection; + + h3c = r->qstream->connection; + c = h3c->connection; + + headers = ngx_array_create(r->pool, 1, sizeof(quiche_h3_header)); + if (headers == NULL) { + return NGX_ERROR; + } + + /* Generate :status pseudo-header. */ + { + h = ngx_array_push(headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) ":status"; + h->name_len = sizeof(":status") - 1; + + h->value = status; + h->value_len = + ngx_sprintf(status, "%03ui", r->headers_out.status) - status; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + /* Generate Server header.*/ + if (r->headers_out.server == NULL) { + h = ngx_array_push(headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "server"; + h->name_len = sizeof("server") - 1; + + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + h->value = (u_char *) NGINX_VER; + h->value_len = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + h->value = (u_char *) NGINX_VER_BUILD; + h->value_len = sizeof(NGINX_VER_BUILD) - 1; + + } else { + h->value = (u_char *) "nginx"; + h->value_len = sizeof("nginx") - 1; + } + } + + /* Generate Date header. */ + if (r->headers_out.date == NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"date: %V\"", + &ngx_cached_http_time); + + h = ngx_array_push(headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "date"; + h->name_len = sizeof("date") - 1; + + h->value = ngx_cached_http_time.data; + h->value_len = ngx_cached_http_time.len; + } + + /* Generate Content-Type header. */ + if (r->headers_out.content_type.len) { + h = ngx_array_push(headers); + if (h == NULL) { + return NGX_ERROR; + } + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + len = r->headers_out.content_type.len + sizeof("; charset=") - 1 + + r->headers_out.charset.len; + + tmp = ngx_pnalloc(r->pool, len); + if (tmp == NULL) { + return NGX_ERROR; + } + + tmp = ngx_cpymem(tmp, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + tmp = ngx_cpymem(tmp, "; charset=", sizeof("; charset=") - 1); + + tmp = ngx_cpymem(tmp, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* updated r->headers_out.content_type is also needed for logging */ + + r->headers_out.content_type.len = len; + r->headers_out.content_type.data = tmp - len; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"content-type: %V\"", + &r->headers_out.content_type); + + h->name = (u_char *) "content-type"; + h->name_len = sizeof("content-type") - 1; + + h->value = r->headers_out.content_type.data; + h->value_len = r->headers_out.content_type.len; + } + + /* Generate Content-Length header. */ + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + h = ngx_array_push(headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "content-length"; + h->name_len = sizeof("content-length") - 1; + + h->value = content_len; + h->value_len = + ngx_sprintf(content_len, "%O", r->headers_out.content_length_n) - + content_len; + } + + /* Generate Last-Modified header. */ + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + h = ngx_array_push(headers); + if (h == NULL) { + return NGX_ERROR; + } + + ngx_http_time(last_modified, r->headers_out.last_modified_time); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"last-modified: %*.s\"", + sizeof(last_modified), last_modified); + + h->name = (u_char *) "last-modified"; + h->name_len = sizeof("last-modified") - 1; + + h->value = last_modified; + h->value_len = sizeof(last_modified); + } + + /* Generate Location header. */ + if (r->headers_out.location && r->headers_out.location->value.len) { + + if (r->headers_out.location->value.data[0] == '/' + && clcf->absolute_redirect) + { + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.len = NGX_SOCKADDR_STRLEN; + host.data = addr; + + if (ngx_connection_local_sockaddr(fc, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + port = ngx_inet_get_port(fc->local_sockaddr); + + location.len = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + +#if (NGX_HTTP_SSL) + if (fc->ssl) + port = (port == 443) ? 0 : port; + else +#endif + port = (port == 80) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + location.len += sizeof(":65535") - 1; + } + + location.data = ngx_pnalloc(r->pool, location.len); + if (location.data == NULL) { + return NGX_ERROR; + } + + tmp = ngx_cpymem(location.data, "http", sizeof("http") - 1); + +#if (NGX_HTTP_SSL) + if (fc->ssl) { + *tmp++ = 's'; + } +#endif + + *tmp++ = ':'; *tmp++ = '/'; *tmp++ = '/'; + tmp = ngx_cpymem(tmp, host.data, host.len); + + if (port) { + tmp = ngx_sprintf(tmp, ":%ui", port); + } + + tmp = ngx_cpymem(tmp, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = tmp - location.data; + r->headers_out.location->value.data = location.data; + ngx_str_set(&r->headers_out.location->key, "Location"); + } + + r->headers_out.location->hash = 0; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"location: %V\"", + &r->headers_out.location->value); + + h = ngx_array_push(headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = (u_char *) "location"; + h->name_len = sizeof("location") - 1; + + h->value = r->headers_out.location->value.data; + h->value_len = r->headers_out.location->value.len; + } + +#if (NGX_HTTP_GZIP) + /* Generate Vary header. */ + if (r->gzip_vary) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"vary: Accept-Encoding\""); + + h->name = (u_char *) "vary"; + h->name_len = sizeof("vary") - 1; + + h->value = (u_char *) "Accept-Encoding"; + h->value_len = sizeof("Accept-Encoding") - 1; + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + /* Generate all other headers. */ + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + h = ngx_array_push(headers); + if (h == NULL) { + return NGX_ERROR; + } + +#if (NGX_DEBUG) + if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 output header: \"%V: %V\"", + &header[i].key, &header[i].value); + } +#endif + + h->name = header[i].key.data; + h->name_len = header[i].key.len; + + h->value = header[i].value.data; + h->value_len = header[i].value.len; + } + + fin = r->header_only + || (r->headers_out.content_length_n == 0 && !r->expect_trailers); + + if (quiche_h3_send_response(h3c->h3, c->quic->conn, r->qstream->id, + headers->elts, headers->nelts, fin)) { + ngx_array_destroy(headers); + return NGX_ERROR; + } + + ngx_post_event(c->write, &ngx_posted_events); + + ngx_array_destroy(headers); + + return NGX_OK; +} + + +static ssize_t +ngx_http_v3_stream_do_send(ngx_connection_t *fc, ngx_buf_t *b, ngx_int_t fin) +{ + ssize_t n; + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_v3_connection_t *h3c; + ngx_http_v3_stream_t *stream; + + uint8_t *buf = b ? b->pos : NULL; + size_t buf_len = b ? ngx_buf_size(b) : 0; + + r = fc->data; + stream = r->qstream; + h3c = stream->connection; + c = h3c->connection; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, fc->log, 0, + "http3 stream %uz to write %uz bytes, fin=%d", + stream->id, buf_len, fin); + + n = quiche_h3_send_body(h3c->h3, c->quic->conn, r->qstream->id, + buf, buf_len, fin); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, stream->connection->connection->log, 0, + "http3 stream written %z bytes", n); + + if (n == QUICHE_ERR_DONE) { + return NGX_AGAIN; + } + + if (n < 0) { + ngx_log_error(NGX_LOG_ERR, fc->log, 0, "stream write failed: %d", n); + return NGX_ERROR; + } + + return n; +} + + +static ngx_chain_t * +ngx_http_v3_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) +{ + ssize_t n, sent; + off_t send, prev_send; + ngx_uint_t blocked, fin; + + ngx_http_request_t *r; + ngx_http_v3_stream_t *stream; + + r = fc->data; + stream = r->qstream; + + send = 0; + + blocked = 0; + + while (in) { + prev_send = send; + + fin = in->buf->last_buf; + + send += ngx_buf_size(in->buf); + + n = ngx_http_v3_stream_do_send(fc, in->buf, fin); + + if (n == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + sent = (n == NGX_AGAIN) ? 0 : n; + + fc->sent += sent; + + in->buf->pos += sent; + + if (in->buf->pos == in->buf->last) { + in = in->next; + } + + if (send - prev_send != sent) { + blocked = 1; + break; + } + + if (fin) { + stream->out_closed = 1; + } + } + + if (blocked) { + if (!stream->blocked) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, stream->connection->connection->log, 0, + "http3 stream blocked %ui", stream->id); + + stream->blocked = 1; + + fc->write->active = 1; + fc->write->ready = 0; + } + } + + ngx_post_event(stream->connection->connection->write, &ngx_posted_events); + + return in; +} + + +void +ngx_http_v3_close_stream(ngx_http_v3_stream_t *stream, ngx_int_t rc) +{ + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_v3_connection_t *h3c; + + h3c = stream->connection; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0, + "http3 close stream %ui", stream->id); + + quiche_conn_stream_shutdown(h3c->connection->quic->conn, stream->id, + QUICHE_SHUTDOWN_READ, 0); + + ngx_rbtree_delete(&h3c->streams, &stream->node); + + fc = stream->request->connection; + + ngx_http_free_request(stream->request, rc); + + ev = fc->read; + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + ev = fc->write; + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + fc->data = h3c->free_fake_connections; + h3c->free_fake_connections = fc; + + h3c->processing--; +} + + +static void +ngx_http_v3_close_stream_handler(ngx_event_t *ev) +{ + ngx_connection_t *fc; + ngx_http_request_t *r; + + fc = ev->data; + r = fc->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http3 close stream handler"); + + if (ev->timedout) { + ngx_log_error(NGX_LOG_INFO, fc->log, NGX_ETIMEDOUT, "client timed out"); + + fc->timedout = 1; + + ngx_http_v3_close_stream(r->qstream, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + ngx_http_v3_close_stream(r->qstream, 0); +} + + +static void +ngx_http_v3_finalize_connection(ngx_http_v3_connection_t *h3c, + ngx_uint_t status) +{ + ngx_event_t *ev; + ngx_connection_t *c, *fc; + ngx_rbtree_node_t *node, *root, *sentinel; + ngx_http_request_t *r; + ngx_http_v3_stream_t *stream; + + c = h3c->connection; + + quiche_conn_close(c->quic->conn, true, status, NULL, 0); + + c->error = 1; + + if (!h3c->processing) { + ngx_http_close_connection(c); + return; + } + + c->read->handler = ngx_http_empty_handler; + c->write->handler = ngx_http_empty_handler; + + sentinel = h3c->streams.sentinel; + + /* Close all pending streams / requests. */ + for ( ;; ) { + root = h3c->streams.root; + + if (root == sentinel) { + break; + } + + node = ngx_rbtree_min(root, sentinel); + + stream = (ngx_http_v3_stream_t *) node; + + r = stream->request; + fc = r->connection; + + fc->error = 1; + + ev = fc->read; + + ev->eof = 1; + ev->handler(ev); + } + + if (h3c->processing) { + return; + } + + ngx_http_close_connection(c); +} + + +static void +ngx_http_v3_pool_cleanup(void *data) +{ + ngx_http_v3_connection_t *h3c = data; + + if (h3c->h3) { + quiche_h3_conn_free(h3c->h3); + } +} diff -uNr a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c --- a/src/http/v3/ngx_http_v3_filter_module.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3_filter_module.c 2020-01-02 21:25:20.000000000 +0800 @@ -0,0 +1,68 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#include +#include +#include +#include + + +static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_v3_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_v3_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_filter_module = { + NGX_MODULE_V1, + &ngx_http_v3_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; + + +static ngx_int_t +ngx_http_v3_header_filter(ngx_http_request_t *r) +{ + if (!r->qstream) { + return ngx_http_next_header_filter(r); + } + + return ngx_http_v3_send_response(r); +} + + +static ngx_int_t +ngx_http_v3_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_v3_header_filter; + + return NGX_OK; +} diff -uNr a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h --- a/src/http/v3/ngx_http_v3.h 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3.h 2020-01-02 21:25:20.000000000 +0800 @@ -0,0 +1,76 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#ifndef _NGX_HTTP_V3_H_INCLUDED_ +#define _NGX_HTTP_V3_H_INCLUDED_ + + +#include +#include +#include +#include + + +#define NGX_HTTP_V3_ALPN_ADVERTISE "\x05h3-18" + + +typedef struct ngx_http_v3_connection_s ngx_http_v3_connection_t; + + +struct ngx_http_v3_connection_s { + quiche_h3_conn *h3; + + ngx_connection_t *connection; + ngx_http_connection_t *http_connection; + + ngx_pool_t *pool; + + ngx_uint_t processing; + + ngx_rbtree_t streams; + ngx_rbtree_node_t streams_sentinel; + + ngx_connection_t *free_fake_connections; +}; + + +struct ngx_http_v3_stream_s { + ngx_rbtree_node_t node; + + uint64_t id; + + ngx_http_request_t *request; + + ngx_http_v3_connection_t *connection; + + ngx_array_t *cookies; + + ngx_http_v3_stream_t *next; + + ngx_uint_t in_closed:1; + ngx_uint_t out_closed:1; + ngx_uint_t skip_data:1; + ngx_uint_t blocked:1; +}; + + +typedef struct { + ngx_str_t name; + ngx_str_t value; +} ngx_http_v3_header_t; + + +void ngx_http_v3_init(ngx_event_t *rev); + +ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r); +ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r); + +ngx_int_t ngx_http_v3_send_response(ngx_http_request_t *r); + +void ngx_http_v3_close_stream(ngx_http_v3_stream_t *stream, ngx_int_t rc); + + +#endif /* _NGX_HTTP_V3_H_INCLUDED_ */ diff -uNr a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c --- a/src/http/v3/ngx_http_v3_module.c 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3_module.c 2020-01-02 21:25:20.000000000 +0800 @@ -0,0 +1,286 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#include +#include +#include +#include + +#include + + +static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); + +static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, + void *parent, void *child); + +static ngx_int_t ngx_http_v3_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static void ngx_http_v3_cleanup_ctx(void *data); + + +static ngx_command_t ngx_http_v3_commands[] = { + + { ngx_string("http3_max_concurrent_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, concurrent_streams), + NULL }, + + { ngx_string("http3_max_requests"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_requests), + NULL }, + + { ngx_string("http3_max_header_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_header_size), + NULL }, + + { ngx_string("http3_initial_max_data"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_data), + NULL }, + + { ngx_string("http3_initial_max_stream_data"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_stream_data), + NULL }, + + { ngx_string("http3_idle_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, idle_timeout), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_v3_module_ctx = { + ngx_http_v3_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_http_v3_create_srv_conf, /* create server configuration */ + ngx_http_v3_merge_srv_conf, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_module = { + NGX_MODULE_V1, + &ngx_http_v3_module_ctx, /* module context */ + ngx_http_v3_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_variable_t ngx_http_v3_variables[] = { + + { ngx_string("http3"), NULL, + ngx_http_v3_variable, 0, + NGX_HTTP_VAR_CHANGEABLE, 0 }, + + ngx_http_null_variable +}; + + +static ngx_int_t +ngx_http_v3_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_v3_variables; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static void * +ngx_http_v3_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_v3_srv_conf_t *h3scf; + + h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t)); + if (h3scf == NULL) { + return NULL; + } + + h3scf->idle_timeout = NGX_CONF_UNSET_MSEC; + h3scf->max_data = NGX_CONF_UNSET_SIZE; + h3scf->max_stream_data = NGX_CONF_UNSET_SIZE; + h3scf->max_requests = NGX_CONF_UNSET_UINT; + h3scf->max_header_size = NGX_CONF_UNSET_SIZE; + h3scf->concurrent_streams = NGX_CONF_UNSET_UINT; + + return h3scf; +} + + +#if (NGX_DEBUG) +static void +quiche_log(const char *line, void *argp) +{ + ngx_log_t *log = ngx_cycle->log; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, line); +} +#endif + + +static char * +ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_v3_srv_conf_t *prev = parent; + ngx_http_v3_srv_conf_t *conf = child; + + ngx_pool_cleanup_t *cln; + + ngx_conf_merge_msec_value(conf->idle_timeout, + prev->idle_timeout, 180000); + + ngx_conf_merge_size_value(conf->max_data, + prev->max_data, 10485760); + + ngx_conf_merge_size_value(conf->max_stream_data, + prev->max_stream_data, 1048576); + + ngx_conf_merge_uint_value(conf->max_requests, + prev->max_requests, 1000); + + ngx_conf_merge_size_value(conf->max_header_size, + prev->max_header_size, 16384); + + ngx_conf_merge_uint_value(conf->concurrent_streams, + prev->concurrent_streams, 128); + + conf->quic.log = cf->log; + +#if (NGX_DEBUG) + /* Enable quiche debug logging. quiche commit ceade4 or later is required */ + quiche_enable_debug_logging(quiche_log, NULL); +#endif + + if (ngx_quic_create_conf(&conf->quic) != NGX_OK) { + return NGX_CONF_ERROR; + } + + quiche_config_set_max_idle_timeout(conf->quic.config, conf->idle_timeout); + + quiche_config_set_initial_max_data(conf->quic.config, conf->max_data); + + quiche_config_set_initial_max_stream_data_bidi_remote(conf->quic.config, + conf->max_stream_data); + + quiche_config_set_initial_max_stream_data_uni(conf->quic.config, + conf->max_stream_data); + + quiche_config_set_initial_max_streams_bidi(conf->quic.config, + conf->concurrent_streams); + + /* For HTTP/3 we only need 3 unidirectional streams. */ + quiche_config_set_initial_max_streams_uni(conf->quic.config, 3); + + conf->http3 = quiche_h3_config_new(); + if (conf->http3 == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "failed to create HTTP/3 config"); + return NGX_CONF_ERROR; + } + + quiche_h3_config_set_max_header_list_size(conf->http3, + conf->max_header_size); + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NGX_CONF_ERROR; + } + + cln->handler = ngx_quic_cleanup_ctx; + cln->data = &conf->quic; + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NGX_CONF_ERROR; + } + + cln->handler = ngx_http_v3_cleanup_ctx; + cln->data = conf->http3; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_v3_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, + uintptr_t data) +{ + ngx_connection_t *c; + + v->valid = 1; + v->no_cacheable = 1; + v->not_found = 0; + + c = r->connection; + if (c == NULL) { + return NGX_ERROR; + } + + if (c->quic != NULL) { + v->len = sizeof("h3") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "h3"; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + return NGX_OK; +} + + +static void +ngx_http_v3_cleanup_ctx(void *data) +{ + quiche_h3_config *config = data; + + quiche_h3_config_free(config); +} diff -uNr a/src/http/v3/ngx_http_v3_module.h b/src/http/v3/ngx_http_v3_module.h --- a/src/http/v3/ngx_http_v3_module.h 1970-01-01 08:00:00.000000000 +0800 +++ b/src/http/v3/ngx_http_v3_module.h 2020-01-02 21:25:20.000000000 +0800 @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Cloudflare, Inc. + */ + + +#ifndef _NGX_HTTP_V3_MODULE_H_INCLUDED_ +#define _NGX_HTTP_V3_MODULE_H_INCLUDED_ + + +#include +#include + +#include + + +typedef struct { + ngx_quic_t quic; + + quiche_h3_config *http3; + + ngx_msec_t idle_timeout; + size_t max_data; + size_t max_stream_data; + ngx_uint_t max_requests; + ngx_uint_t max_header_size; + ngx_uint_t concurrent_streams; +} ngx_http_v3_srv_conf_t; + + +extern ngx_module_t ngx_http_v3_module; + + +#endif /* _NGX_HTTP_V3_MODULE_H_INCLUDED_ */ ================================================ FILE: openssl-1.1.1.patch ================================================ Add TLS 1.3 Support. Add BoringSSL's Equal Preference Support. Add ChaCha20-Poly1305 Draft Version Support. Using: patch -p1 < openssl-1.1.1.patch diff --color -uNr a/crypto/evp/c_allc.c b/crypto/evp/c_allc.c --- a/crypto/evp/c_allc.c 2022-11-01 20:36:10.000000000 +0800 +++ b/crypto/evp/c_allc.c 2022-12-25 15:28:59.857389551 +0800 @@ -261,6 +261,7 @@ EVP_add_cipher(EVP_chacha20()); # ifndef OPENSSL_NO_POLY1305 EVP_add_cipher(EVP_chacha20_poly1305()); + EVP_add_cipher(EVP_chacha20_poly1305_draft()); # endif #endif } diff --color -uNr a/crypto/evp/e_chacha20_poly1305.c b/crypto/evp/e_chacha20_poly1305.c --- a/crypto/evp/e_chacha20_poly1305.c 2022-11-01 20:36:10.000000000 +0800 +++ b/crypto/evp/e_chacha20_poly1305.c 2022-12-25 15:28:59.857389551 +0800 @@ -156,6 +156,7 @@ struct { uint64_t aad, text; } len; int aad, mac_inited, tag_len, nonce_len; size_t tls_payload_length; + unsigned char draft:1; } EVP_CHACHA_AEAD_CTX; # define NO_TLS_PAYLOAD_LENGTH ((size_t)-1) @@ -176,6 +177,7 @@ actx->aad = 0; actx->mac_inited = 0; actx->tls_payload_length = NO_TLS_PAYLOAD_LENGTH; + actx->draft = 0; if (iv != NULL) { unsigned char temp[CHACHA_CTR_SIZE] = { 0 }; @@ -197,6 +199,27 @@ return 1; } +static int chacha20_poly1305_draft_init_key(EVP_CIPHER_CTX *ctx, + const unsigned char *inkey, + const unsigned char *iv, int enc) +{ + EVP_CHACHA_AEAD_CTX *actx = aead_data(ctx); + + if (!inkey) + return 1; + + actx->len.aad = 0; + actx->len.text = 0; + actx->aad = 0; + actx->mac_inited = 0; + actx->tls_payload_length = NO_TLS_PAYLOAD_LENGTH; + actx->draft = 1; + + chacha_init_key(ctx, inkey, NULL, enc); + + return 1; +} + # if !defined(OPENSSL_SMALL_FOOTPRINT) # if defined(POLY1305_ASM) && (defined(__x86_64) || defined(__x86_64__) || \ @@ -367,10 +390,11 @@ { EVP_CHACHA_AEAD_CTX *actx = aead_data(ctx); size_t rem, plen = actx->tls_payload_length; + uint64_t thirteen = EVP_AEAD_TLS1_AAD_LEN; if (!actx->mac_inited) { # if !defined(OPENSSL_SMALL_FOOTPRINT) - if (plen != NO_TLS_PAYLOAD_LENGTH && out != NULL) + if (plen != NO_TLS_PAYLOAD_LENGTH && out != NULL && !actx->draft) return chacha20_poly1305_tls_cipher(ctx, out, in, len); # endif actx->key.counter[0] = 0; @@ -397,9 +421,14 @@ return len; } else { /* plain- or ciphertext */ if (actx->aad) { /* wrap up aad */ - if ((rem = (size_t)actx->len.aad % POLY1305_BLOCK_SIZE)) - Poly1305_Update(POLY1305_ctx(actx), zero, - POLY1305_BLOCK_SIZE - rem); + if (actx->draft) { + thirteen = actx->len.aad; + Poly1305_Update(POLY1305_ctx(actx), (const unsigned char *)&thirteen, sizeof(thirteen)); + } else { + if ((rem = (size_t)actx->len.aad % POLY1305_BLOCK_SIZE)) + Poly1305_Update(POLY1305_ctx(actx), zero, + POLY1305_BLOCK_SIZE - rem); + } actx->aad = 0; } @@ -432,40 +461,52 @@ } is_endian = { 1 }; unsigned char temp[POLY1305_BLOCK_SIZE]; + if (actx->draft) { + thirteen = actx->len.text; + Poly1305_Update(POLY1305_ctx(actx), (const unsigned char *)&thirteen, sizeof(thirteen)); + } + if (actx->aad) { /* wrap up aad */ - if ((rem = (size_t)actx->len.aad % POLY1305_BLOCK_SIZE)) - Poly1305_Update(POLY1305_ctx(actx), zero, - POLY1305_BLOCK_SIZE - rem); + if (actx->draft) { + thirteen = actx->len.aad; + Poly1305_Update(POLY1305_ctx(actx), (const unsigned char *)&thirteen, sizeof(thirteen)); + } else { + if ((rem = (size_t)actx->len.aad % POLY1305_BLOCK_SIZE)) + Poly1305_Update(POLY1305_ctx(actx), zero, + POLY1305_BLOCK_SIZE - rem); + } actx->aad = 0; } - if ((rem = (size_t)actx->len.text % POLY1305_BLOCK_SIZE)) - Poly1305_Update(POLY1305_ctx(actx), zero, - POLY1305_BLOCK_SIZE - rem); + if (!actx->draft) { + if ((rem = (size_t)actx->len.text % POLY1305_BLOCK_SIZE)) + Poly1305_Update(POLY1305_ctx(actx), zero, + POLY1305_BLOCK_SIZE - rem); - if (is_endian.little) { - Poly1305_Update(POLY1305_ctx(actx), - (unsigned char *)&actx->len, POLY1305_BLOCK_SIZE); - } else { - temp[0] = (unsigned char)(actx->len.aad); - temp[1] = (unsigned char)(actx->len.aad>>8); - temp[2] = (unsigned char)(actx->len.aad>>16); - temp[3] = (unsigned char)(actx->len.aad>>24); - temp[4] = (unsigned char)(actx->len.aad>>32); - temp[5] = (unsigned char)(actx->len.aad>>40); - temp[6] = (unsigned char)(actx->len.aad>>48); - temp[7] = (unsigned char)(actx->len.aad>>56); - - temp[8] = (unsigned char)(actx->len.text); - temp[9] = (unsigned char)(actx->len.text>>8); - temp[10] = (unsigned char)(actx->len.text>>16); - temp[11] = (unsigned char)(actx->len.text>>24); - temp[12] = (unsigned char)(actx->len.text>>32); - temp[13] = (unsigned char)(actx->len.text>>40); - temp[14] = (unsigned char)(actx->len.text>>48); - temp[15] = (unsigned char)(actx->len.text>>56); + if (is_endian.little) { + Poly1305_Update(POLY1305_ctx(actx), + (unsigned char *)&actx->len, POLY1305_BLOCK_SIZE); + } else { + temp[0] = (unsigned char)(actx->len.aad); + temp[1] = (unsigned char)(actx->len.aad>>8); + temp[2] = (unsigned char)(actx->len.aad>>16); + temp[3] = (unsigned char)(actx->len.aad>>24); + temp[4] = (unsigned char)(actx->len.aad>>32); + temp[5] = (unsigned char)(actx->len.aad>>40); + temp[6] = (unsigned char)(actx->len.aad>>48); + temp[7] = (unsigned char)(actx->len.aad>>56); + + temp[8] = (unsigned char)(actx->len.text); + temp[9] = (unsigned char)(actx->len.text>>8); + temp[10] = (unsigned char)(actx->len.text>>16); + temp[11] = (unsigned char)(actx->len.text>>24); + temp[12] = (unsigned char)(actx->len.text>>32); + temp[13] = (unsigned char)(actx->len.text>>40); + temp[14] = (unsigned char)(actx->len.text>>48); + temp[15] = (unsigned char)(actx->len.text>>56); - Poly1305_Update(POLY1305_ctx(actx), temp, POLY1305_BLOCK_SIZE); + Poly1305_Update(POLY1305_ctx(actx), temp, POLY1305_BLOCK_SIZE); + } } Poly1305_Final(POLY1305_ctx(actx), ctx->encrypt ? actx->tag : temp); @@ -539,12 +580,14 @@ return 1; case EVP_CTRL_AEAD_SET_IVLEN: + if (actx->draft) return -1; if (arg <= 0 || arg > CHACHA20_POLY1305_MAX_IVLEN) return 0; actx->nonce_len = arg; return 1; case EVP_CTRL_AEAD_SET_IV_FIXED: + if (actx->draft) return -1; if (arg != 12) return 0; actx->nonce[0] = actx->key.counter[1] @@ -629,9 +672,32 @@ NULL /* app_data */ }; +static EVP_CIPHER chacha20_poly1305_draft = { + NID_chacha20_poly1305_draft, + 1, /* block_size */ + CHACHA_KEY_SIZE, /* key_len */ + 0, /* iv_len, none */ + EVP_CIPH_FLAG_AEAD_CIPHER | EVP_CIPH_CUSTOM_IV | + EVP_CIPH_ALWAYS_CALL_INIT | EVP_CIPH_CTRL_INIT | + EVP_CIPH_CUSTOM_COPY | EVP_CIPH_FLAG_CUSTOM_CIPHER, + chacha20_poly1305_draft_init_key, + chacha20_poly1305_cipher, + chacha20_poly1305_cleanup, + 0, /* 0 moves context-specific structure allocation to ctrl */ + NULL, /* set_asn1_parameters */ + NULL, /* get_asn1_parameters */ + chacha20_poly1305_ctrl, + NULL /* app_data */ +}; + const EVP_CIPHER *EVP_chacha20_poly1305(void) { return(&chacha20_poly1305); } + +const EVP_CIPHER *EVP_chacha20_poly1305_draft(void) +{ + return(&chacha20_poly1305_draft); +} # endif #endif diff --color -uNr a/crypto/objects/obj_dat.h b/crypto/objects/obj_dat.h --- a/crypto/objects/obj_dat.h 2022-11-01 20:36:10.000000000 +0800 +++ b/crypto/objects/obj_dat.h 2022-12-25 15:28:59.861389655 +0800 @@ -1078,7 +1078,7 @@ 0x2A,0x86,0x48,0x86,0xF7,0x0D,0x02,0x0D, /* [ 7753] OBJ_hmacWithSHA512_256 */ }; -#define NUM_NID 1195 +#define NUM_NID 1196 static const ASN1_OBJECT nid_objs[NUM_NID] = { {"UNDEF", "undefined", NID_undef}, {"rsadsi", "RSA Data Security, Inc.", NID_rsadsi, 6, &so[0]}, @@ -2275,9 +2275,10 @@ {"magma-mac", "magma-mac", NID_magma_mac}, {"hmacWithSHA512-224", "hmacWithSHA512-224", NID_hmacWithSHA512_224, 8, &so[7745]}, {"hmacWithSHA512-256", "hmacWithSHA512-256", NID_hmacWithSHA512_256, 8, &so[7753]}, + {"ChaCha20-Poly1305-D", "chacha20-poly1305-draft", NID_chacha20_poly1305_draft}, }; -#define NUM_SN 1186 +#define NUM_SN 1187 static const unsigned int sn_objs[NUM_SN] = { 364, /* "AD_DVCS" */ 419, /* "AES-128-CBC" */ @@ -2395,6 +2396,7 @@ 417, /* "CSPName" */ 1019, /* "ChaCha20" */ 1018, /* "ChaCha20-Poly1305" */ + 1195, /* "ChaCha20-Poly1305-D" */ 367, /* "CrlID" */ 391, /* "DC" */ 31, /* "DES-CBC" */ @@ -3467,7 +3469,7 @@ 1093, /* "x509ExtAdmission" */ }; -#define NUM_LN 1186 +#define NUM_LN 1187 static const unsigned int ln_objs[NUM_LN] = { 363, /* "AD Time Stamping" */ 405, /* "ANSI X9.62" */ @@ -3846,6 +3848,7 @@ 883, /* "certificateRevocationList" */ 1019, /* "chacha20" */ 1018, /* "chacha20-poly1305" */ + 1195, /* "chacha20-poly1305-draft" */ 54, /* "challengePassword" */ 407, /* "characteristic-two-field" */ 395, /* "clearance" */ diff --color -uNr a/crypto/objects/objects.txt b/crypto/objects/objects.txt --- a/crypto/objects/objects.txt 2022-11-01 20:36:10.000000000 +0800 +++ b/crypto/objects/objects.txt 2022-12-25 15:28:59.862389681 +0800 @@ -1534,6 +1534,7 @@ : AES-192-CBC-HMAC-SHA256 : aes-192-cbc-hmac-sha256 : AES-256-CBC-HMAC-SHA256 : aes-256-cbc-hmac-sha256 : ChaCha20-Poly1305 : chacha20-poly1305 + : ChaCha20-Poly1305-D : chacha20-poly1305-draft : ChaCha20 : chacha20 ISO-US 10046 2 1 : dhpublicnumber : X9.42 DH diff --color -uNr a/crypto/objects/obj_mac.num b/crypto/objects/obj_mac.num --- a/crypto/objects/obj_mac.num 2022-11-01 20:36:10.000000000 +0800 +++ b/crypto/objects/obj_mac.num 2022-12-25 15:28:59.862389681 +0800 @@ -1192,3 +1192,4 @@ magma_mac 1192 hmacWithSHA512_224 1193 hmacWithSHA512_256 1194 +chacha20_poly1305_draft 1195 diff --color -uNr a/doc/man1/ciphers.pod b/doc/man1/ciphers.pod --- a/doc/man1/ciphers.pod 2022-11-01 20:36:10.000000000 +0800 +++ b/doc/man1/ciphers.pod 2022-12-25 15:28:59.863389707 +0800 @@ -400,6 +400,21 @@ =back +=head1 EQUAL PREFERENCE GROUPS + +If configuring a server, one may also configure equal-preference groups to +partially respect the client's preferences when +B is enabled. Ciphers in an equal-preference +group have equal priority and use the client order. This may be used to +enforce that AEADs are preferred but select AES-GCM vs. ChaCha20-Poly1305 +based on client preferences. An equal-preference is specified with square +brackets, combining multiple selectors separated by |. For example: + + [ECDHE-ECDSA-CHACHA20-POLY1305|ECDHE-ECDSA-AES128-GCM-SHA256] + + Once an equal-preference group is used, future directives must be + opcode-less. + =head1 CIPHER SUITE NAMES The following lists give the SSL or TLS cipher suites names from the diff --color -uNr a/include/openssl/evp.h b/include/openssl/evp.h --- a/include/openssl/evp.h 2022-11-01 20:36:10.000000000 +0800 +++ b/include/openssl/evp.h 2022-12-25 15:28:59.864389733 +0800 @@ -919,6 +919,7 @@ const EVP_CIPHER *EVP_chacha20(void); # ifndef OPENSSL_NO_POLY1305 const EVP_CIPHER *EVP_chacha20_poly1305(void); +const EVP_CIPHER *EVP_chacha20_poly1305_draft(void); # endif # endif diff --color -uNr a/include/openssl/obj_mac.h b/include/openssl/obj_mac.h --- a/include/openssl/obj_mac.h 2022-11-01 20:36:10.000000000 +0800 +++ b/include/openssl/obj_mac.h 2022-12-25 15:28:59.866389785 +0800 @@ -4807,6 +4807,10 @@ #define LN_chacha20_poly1305 "chacha20-poly1305" #define NID_chacha20_poly1305 1018 +#define SN_chacha20_poly1305_draft "ChaCha20-Poly1305-D" +#define LN_chacha20_poly1305_draft "chacha20-poly1305-draft" +#define NID_chacha20_poly1305_draft 1195 + #define SN_chacha20 "ChaCha20" #define LN_chacha20 "chacha20" #define NID_chacha20 1019 diff --color -uNr a/include/openssl/sslerr.h b/include/openssl/sslerr.h --- a/include/openssl/sslerr.h 2022-11-01 20:36:10.000000000 +0800 +++ b/include/openssl/sslerr.h 2022-12-25 15:28:59.867389811 +0800 @@ -603,6 +603,8 @@ # define SSL_R_MISSING_SUPPORTED_GROUPS_EXTENSION 209 # define SSL_R_MISSING_TMP_DH_KEY 171 # define SSL_R_MISSING_TMP_ECDH_KEY 311 +# define SSL_R_MIXED_SPECIAL_OPERATOR_WITH_GROUPS 101 +# define SSL_R_NESTED_GROUP 108 # define SSL_R_MIXED_HANDSHAKE_AND_NON_HANDSHAKE_DATA 293 # define SSL_R_NOT_ON_RECORD_BOUNDARY 182 # define SSL_R_NOT_REPLACING_CERTIFICATE 289 @@ -735,9 +737,11 @@ # define SSL_R_UNABLE_TO_FIND_PUBLIC_KEY_PARAMETERS 239 # define SSL_R_UNABLE_TO_LOAD_SSL3_MD5_ROUTINES 242 # define SSL_R_UNABLE_TO_LOAD_SSL3_SHA1_ROUTINES 243 +# define SSL_R_UNEXPECTED_GROUP_CLOSE 109 # define SSL_R_UNEXPECTED_CCS_MESSAGE 262 # define SSL_R_UNEXPECTED_END_OF_EARLY_DATA 178 # define SSL_R_UNEXPECTED_MESSAGE 244 +# define SSL_R_UNEXPECTED_OPERATOR_IN_GROUP 110 # define SSL_R_UNEXPECTED_RECORD 245 # define SSL_R_UNINITIALIZED 276 # define SSL_R_UNKNOWN_ALERT_TYPE 246 diff --color -uNr a/include/openssl/ssl.h b/include/openssl/ssl.h --- a/include/openssl/ssl.h 2022-11-01 20:36:10.000000000 +0800 +++ b/include/openssl/ssl.h 2022-12-25 15:28:59.868389837 +0800 @@ -125,6 +125,7 @@ # define SSL_TXT_CAMELLIA256 "CAMELLIA256" # define SSL_TXT_CAMELLIA "CAMELLIA" # define SSL_TXT_CHACHA20 "CHACHA20" +# define SSL_TXT_CHACHA20_D "CHACHA20-D" # define SSL_TXT_GOST "GOST89" # define SSL_TXT_ARIA "ARIA" # define SSL_TXT_ARIA_GCM "ARIAGCM" diff --color -uNr a/include/openssl/tls1.h b/include/openssl/tls1.h --- a/include/openssl/tls1.h 2022-11-01 20:36:10.000000000 +0800 +++ b/include/openssl/tls1.h 2022-12-25 15:28:59.869389863 +0800 @@ -597,7 +597,12 @@ # define TLS1_CK_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 0x0300C09A # define TLS1_CK_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 0x0300C09B -/* draft-ietf-tls-chacha20-poly1305-03 */ +/* Chacha20-Poly1305-Draft ciphersuites from draft-agl-tls-chacha20poly1305-04 */ +# define TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_D 0x0300CC13 +# define TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_D 0x0300CC14 +# define TLS1_CK_DHE_RSA_WITH_CHACHA20_POLY1305_D 0x0300CC15 + +/* Chacha20-Poly1305 ciphersuites from RFC7905 */ # define TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305 0x0300CCA8 # define TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 0x0300CCA9 # define TLS1_CK_DHE_RSA_WITH_CHACHA20_POLY1305 0x0300CCAA @@ -762,6 +767,9 @@ # define TLS1_RFC_DHE_RSA_WITH_CHACHA20_POLY1305 "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256" # define TLS1_RFC_ECDHE_RSA_WITH_CHACHA20_POLY1305 "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" # define TLS1_RFC_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" +# define TLS1_RFC_DHE_RSA_WITH_CHACHA20_POLY1305_D "OLD_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256" +# define TLS1_RFC_ECDHE_RSA_WITH_CHACHA20_POLY1305_D "OLD_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" +# define TLS1_RFC_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_D "OLD_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" # define TLS1_RFC_PSK_WITH_CHACHA20_POLY1305 "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256" # define TLS1_RFC_ECDHE_PSK_WITH_CHACHA20_POLY1305 "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256" # define TLS1_RFC_DHE_PSK_WITH_CHACHA20_POLY1305 "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256" @@ -1090,7 +1098,12 @@ # define TLS1_TXT_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 "ECDH-RSA-CAMELLIA128-SHA256" # define TLS1_TXT_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 "ECDH-RSA-CAMELLIA256-SHA384" -/* draft-ietf-tls-chacha20-poly1305-03 */ +/* Chacha20-Poly1305-Draft ciphersuites from draft-agl-tls-chacha20poly1305-04 */ +# define TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305_D "ECDHE-RSA-CHACHA20-POLY1305-OLD" +# define TLS1_TXT_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_D "ECDHE-ECDSA-CHACHA20-POLY1305-OLD" +# define TLS1_TXT_DHE_RSA_WITH_CHACHA20_POLY1305_D "DHE-RSA-CHACHA20-POLY1305-OLD" + +/* Chacha20-Poly1305 ciphersuites from RFC7905 */ # define TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305 "ECDHE-RSA-CHACHA20-POLY1305" # define TLS1_TXT_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 "ECDHE-ECDSA-CHACHA20-POLY1305" # define TLS1_TXT_DHE_RSA_WITH_CHACHA20_POLY1305 "DHE-RSA-CHACHA20-POLY1305" diff --color -uNr a/ssl/s3_lib.c b/ssl/s3_lib.c --- a/ssl/s3_lib.c 2022-11-01 20:36:10.000000000 +0800 +++ b/ssl/s3_lib.c 2022-12-25 15:28:59.870389889 +0800 @@ -31,8 +31,26 @@ }; /* The list of available TLSv1.3 ciphers */ +/* Since nginx can not set the TLS 1.3 cipher, remove it temporarily. */ static SSL_CIPHER tls13_ciphers[] = { { + 0, + } +}; + +/* + * The list of available ciphers, mostly organized into the following + * groups: + * Always there + * EC + * PSK + * SRP (within that: RSA EC PSK) + * Cipher families: Chacha/poly, Camellia, Gost, IDEA, SEED + * Weak ciphers + */ +static SSL_CIPHER ssl3_ciphers[] = { + /* TLSv1.3 ciphers */ + { 1, TLS1_3_RFC_AES_128_GCM_SHA256, TLS1_3_RFC_AES_128_GCM_SHA256, @@ -111,20 +129,8 @@ SSL_HANDSHAKE_MAC_SHA256, 128, 128, - } -}; - -/* - * The list of available ciphers, mostly organized into the following - * groups: - * Always there - * EC - * PSK - * SRP (within that: RSA EC PSK) - * Cipher families: Chacha/poly, Camellia, Gost, IDEA, SEED - * Weak ciphers - */ -static SSL_CIPHER ssl3_ciphers[] = { + }, + /* List of cipher below TLSv1.3 */ { 1, SSL3_TXT_RSA_NULL_MD5, @@ -167,7 +173,7 @@ SSL_aRSA, SSL_3DES, SSL_SHA1, - SSL3_VERSION, TLS1_2_VERSION, + SSL3_VERSION, TLS1_VERSION, DTLS1_BAD_VER, DTLS1_2_VERSION, SSL_NOT_DEFAULT | SSL_MEDIUM | SSL_FIPS, SSL_HANDSHAKE_MAC_DEFAULT | TLS1_PRF, @@ -232,7 +238,7 @@ SSL_aRSA, SSL_AES128, SSL_SHA1, - SSL3_VERSION, TLS1_2_VERSION, + SSL3_VERSION, TLS1_VERSION, DTLS1_BAD_VER, DTLS1_2_VERSION, SSL_HIGH | SSL_FIPS, SSL_HANDSHAKE_MAC_DEFAULT | TLS1_PRF, @@ -296,7 +302,7 @@ SSL_aRSA, SSL_AES256, SSL_SHA1, - SSL3_VERSION, TLS1_2_VERSION, + SSL3_VERSION, TLS1_VERSION, DTLS1_BAD_VER, DTLS1_2_VERSION, SSL_HIGH | SSL_FIPS, SSL_HANDSHAKE_MAC_DEFAULT | TLS1_PRF, @@ -2083,6 +2089,54 @@ 256, }, { + 1, + TLS1_TXT_DHE_RSA_WITH_CHACHA20_POLY1305_D, + TLS1_RFC_DHE_RSA_WITH_CHACHA20_POLY1305_D, + TLS1_CK_DHE_RSA_WITH_CHACHA20_POLY1305_D, + SSL_kDHE, + SSL_aRSA, + SSL_CHACHA20POLY1305_D, + SSL_AEAD, + TLS1_2_VERSION, TLS1_2_VERSION, + DTLS1_2_VERSION, DTLS1_2_VERSION, + SSL_HIGH, + SSL_HANDSHAKE_MAC_SHA256 | TLS1_PRF_SHA256, + 256, + 256, + }, + { + 1, + TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305_D, + TLS1_RFC_ECDHE_RSA_WITH_CHACHA20_POLY1305_D, + TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_D, + SSL_kECDHE, + SSL_aRSA, + SSL_CHACHA20POLY1305_D, + SSL_AEAD, + TLS1_2_VERSION, TLS1_2_VERSION, + DTLS1_2_VERSION, DTLS1_2_VERSION, + SSL_HIGH, + SSL_HANDSHAKE_MAC_SHA256 | TLS1_PRF_SHA256, + 256, + 256, + }, + { + 1, + TLS1_TXT_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_D, + TLS1_RFC_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_D, + TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_D, + SSL_kECDHE, + SSL_aECDSA, + SSL_CHACHA20POLY1305_D, + SSL_AEAD, + TLS1_2_VERSION, TLS1_2_VERSION, + DTLS1_2_VERSION, DTLS1_2_VERSION, + SSL_HIGH, + SSL_HANDSHAKE_MAC_SHA256 | TLS1_PRF_SHA256, + 256, + 256, + }, + { 1, TLS1_TXT_PSK_WITH_CHACHA20_POLY1305, TLS1_RFC_PSK_WITH_CHACHA20_POLY1305, @@ -4127,6 +4181,17 @@ return 1; } +struct ssl_cipher_preference_list_st* ssl_get_cipher_preferences(SSL *s) +{ + if (s->cipher_list != NULL) + return (s->cipher_list); + + if ((s->ctx != NULL) && (s->ctx->cipher_list != NULL)) + return (s->ctx->cipher_list); + + return NULL; +} + /* * ssl3_choose_cipher - choose a cipher from those offered by the client * @s: SSL connection @@ -4136,16 +4201,24 @@ * Returns the selected cipher or NULL when no common ciphers. */ const SSL_CIPHER *ssl3_choose_cipher(SSL *s, STACK_OF(SSL_CIPHER) *clnt, - STACK_OF(SSL_CIPHER) *srvr) + struct ssl_cipher_preference_list_st + *server_pref) { const SSL_CIPHER *c, *ret = NULL; - STACK_OF(SSL_CIPHER) *prio, *allow; - int i, ii, ok, prefer_sha256 = 0; + STACK_OF(SSL_CIPHER) *srvr = server_pref->ciphers, *prio, *allow; + int i, ii, ok, prefer_sha256 = 0, safari_ec = 0; unsigned long alg_k = 0, alg_a = 0, mask_k = 0, mask_a = 0; const EVP_MD *mdsha256 = EVP_sha256(); -#ifndef OPENSSL_NO_CHACHA - STACK_OF(SSL_CIPHER) *prio_chacha = NULL; -#endif + + /* in_group_flags will either be NULL, or will point to an array of + * bytes which indicate equal-preference groups in the |prio| stack. + * See the comment about |in_group_flags| in the + * |ssl_cipher_preference_list_st| struct. */ + const uint8_t *in_group_flags; + + /* group_min contains the minimal index so far found in a group, or -1 + * if no such value exists yet. */ + int group_min = -1; /* Let's see which ciphers we can support */ @@ -4172,54 +4245,13 @@ #endif /* SUITE-B takes precedence over server preference and ChaCha priortiy */ - if (tls1_suiteb(s)) { + if (s->options & SSL_OP_CIPHER_SERVER_PREFERENCE || tls1_suiteb(s)) { prio = srvr; + in_group_flags = server_pref->in_group_flags; allow = clnt; - } else if (s->options & SSL_OP_CIPHER_SERVER_PREFERENCE) { - prio = srvr; - allow = clnt; -#ifndef OPENSSL_NO_CHACHA - /* If ChaCha20 is at the top of the client preference list, - and there are ChaCha20 ciphers in the server list, then - temporarily prioritize all ChaCha20 ciphers in the servers list. */ - if (s->options & SSL_OP_PRIORITIZE_CHACHA && sk_SSL_CIPHER_num(clnt) > 0) { - c = sk_SSL_CIPHER_value(clnt, 0); - if (c->algorithm_enc == SSL_CHACHA20POLY1305) { - /* ChaCha20 is client preferred, check server... */ - int num = sk_SSL_CIPHER_num(srvr); - int found = 0; - for (i = 0; i < num; i++) { - c = sk_SSL_CIPHER_value(srvr, i); - if (c->algorithm_enc == SSL_CHACHA20POLY1305) { - found = 1; - break; - } - } - if (found) { - prio_chacha = sk_SSL_CIPHER_new_reserve(NULL, num); - /* if reserve fails, then there's likely a memory issue */ - if (prio_chacha != NULL) { - /* Put all ChaCha20 at the top, starting with the one we just found */ - sk_SSL_CIPHER_push(prio_chacha, c); - for (i++; i < num; i++) { - c = sk_SSL_CIPHER_value(srvr, i); - if (c->algorithm_enc == SSL_CHACHA20POLY1305) - sk_SSL_CIPHER_push(prio_chacha, c); - } - /* Pull in the rest */ - for (i = 0; i < num; i++) { - c = sk_SSL_CIPHER_value(srvr, i); - if (c->algorithm_enc != SSL_CHACHA20POLY1305) - sk_SSL_CIPHER_push(prio_chacha, c); - } - prio = prio_chacha; - } - } - } - } -# endif } else { prio = clnt; + in_group_flags = NULL; allow = srvr; } @@ -4250,14 +4282,16 @@ for (i = 0; i < sk_SSL_CIPHER_num(prio); i++) { c = sk_SSL_CIPHER_value(prio, i); + ok = 1; + /* Skip ciphers not supported by the protocol version */ if (!SSL_IS_DTLS(s) && ((s->version < c->min_tls) || (s->version > c->max_tls))) - continue; + ok = 0; if (SSL_IS_DTLS(s) && (DTLS_VERSION_LT(s->version, c->min_dtls) || DTLS_VERSION_GT(s->version, c->max_dtls))) - continue; + ok = 0; /* * Since TLS 1.3 ciphersuites can be used with any auth or @@ -4279,10 +4313,10 @@ #ifndef OPENSSL_NO_PSK /* with PSK there must be server callback set */ if ((alg_k & SSL_PSK) && s->psk_server_callback == NULL) - continue; + ok = 0; #endif /* OPENSSL_NO_PSK */ - ok = (alg_k & mask_k) && (alg_a & mask_a); + ok = ok && (alg_k & mask_k) && (alg_a & mask_a); #ifdef CIPHER_DEBUG fprintf(stderr, "%d:[%08lX:%08lX:%08lX:%08lX]%p:%s\n", ok, alg_k, alg_a, mask_k, mask_a, (void *)c, c->name); @@ -4299,6 +4333,14 @@ if (!ok) continue; + + safari_ec = 0; +#if !defined(OPENSSL_NO_EC) + if ((alg_k & SSL_kECDHE) && (alg_a & SSL_aECDSA)) { + if (s->s3->is_probably_safari) + safari_ec = 1; + } +#endif } ii = sk_SSL_CIPHER_find(allow, c); if (ii >= 0) { @@ -4306,14 +4348,7 @@ if (!ssl_security(s, SSL_SECOP_CIPHER_SHARED, c->strength_bits, 0, (void *)c)) continue; -#if !defined(OPENSSL_NO_EC) - if ((alg_k & SSL_kECDHE) && (alg_a & SSL_aECDSA) - && s->s3->is_probably_safari) { - if (!ret) - ret = sk_SSL_CIPHER_value(allow, ii); - continue; - } -#endif + if (prefer_sha256) { const SSL_CIPHER *tmp = sk_SSL_CIPHER_value(allow, ii); @@ -4325,13 +4360,38 @@ ret = tmp; continue; } - ret = sk_SSL_CIPHER_value(allow, ii); + + if (in_group_flags != NULL && in_group_flags[i] == 1) { + /* This element of |prio| is in a group. Update + * the minimum index found so far and continue + * looking. */ + if (group_min == -1 || group_min > ii) + group_min = ii; + } else { + if (group_min != -1 && group_min < ii) + ii = group_min; + if (safari_ec) { + if (!ret) + ret = sk_SSL_CIPHER_value(allow, ii); + continue; + } + ret = sk_SSL_CIPHER_value(allow, ii); + break; + } + } + + if (in_group_flags != NULL && !in_group_flags[i] && group_min != -1) { + /* We are about to leave a group, but we found a match + * in it, so that's our answer. */ + if (safari_ec) { + if (!ret) + ret = sk_SSL_CIPHER_value(allow, group_min); + continue; + } + ret = sk_SSL_CIPHER_value(allow, group_min); break; } } -#ifndef OPENSSL_NO_CHACHA - sk_SSL_CIPHER_free(prio_chacha); -#endif return ret; } diff --color -uNr a/ssl/ssl_ciph.c b/ssl/ssl_ciph.c --- a/ssl/ssl_ciph.c 2022-11-01 20:36:10.000000000 +0800 +++ b/ssl/ssl_ciph.c 2022-12-25 15:35:32.000000000 +0800 @@ -43,7 +43,8 @@ #define SSL_ENC_CHACHA_IDX 19 #define SSL_ENC_ARIA128GCM_IDX 20 #define SSL_ENC_ARIA256GCM_IDX 21 -#define SSL_ENC_NUM_IDX 22 +#define SSL_ENC_CHACHA20_D_IDX 22 +#define SSL_ENC_NUM_IDX 23 /* NB: make sure indices in these tables match values above */ @@ -76,6 +77,7 @@ {SSL_CHACHA20POLY1305, NID_chacha20_poly1305}, /* SSL_ENC_CHACHA_IDX 19 */ {SSL_ARIA128GCM, NID_aria_128_gcm}, /* SSL_ENC_ARIA128GCM_IDX 20 */ {SSL_ARIA256GCM, NID_aria_256_gcm}, /* SSL_ENC_ARIA256GCM_IDX 21 */ + {SSL_CHACHA20POLY1305_D, NID_chacha20_poly1305_draft}, /* SSL_ENC_CHACHA20POLY1305_IDX 22 */ }; static const EVP_CIPHER *ssl_cipher_methods[SSL_ENC_NUM_IDX]; @@ -192,6 +194,7 @@ const SSL_CIPHER *cipher; int active; int dead; + int in_group; struct cipher_order_st *next, *prev; } CIPHER_ORDER; @@ -275,6 +278,7 @@ {0, SSL_TXT_CAMELLIA256, NULL, 0, 0, 0, SSL_CAMELLIA256}, {0, SSL_TXT_CAMELLIA, NULL, 0, 0, 0, SSL_CAMELLIA}, {0, SSL_TXT_CHACHA20, NULL, 0, 0, 0, SSL_CHACHA20}, + {0, SSL_TXT_CHACHA20_D, NULL, 0, 0, 0, SSL_CHACHA20POLY1305_D}, {0, SSL_TXT_ARIA, NULL, 0, 0, 0, SSL_ARIA}, {0, SSL_TXT_ARIA_GCM, NULL, 0, 0, 0, SSL_ARIA128GCM | SSL_ARIA256GCM}, @@ -296,6 +300,7 @@ {0, SSL_TXT_TLSV1, NULL, 0, 0, 0, 0, 0, TLS1_VERSION}, {0, "TLSv1.0", NULL, 0, 0, 0, 0, 0, TLS1_VERSION}, {0, SSL_TXT_TLSV1_2, NULL, 0, 0, 0, 0, 0, TLS1_2_VERSION}, + {0, "TLS13", NULL, 0, 0, 0, 0, 0, TLS1_3_VERSION}, /* strength classes */ {0, SSL_TXT_LOW, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, SSL_LOW}, @@ -681,6 +686,7 @@ co_list[co_list_num].next = NULL; co_list[co_list_num].prev = NULL; co_list[co_list_num].active = 0; + co_list[co_list_num].in_group = 0; co_list_num++; } @@ -774,8 +780,8 @@ uint32_t alg_auth, uint32_t alg_enc, uint32_t alg_mac, int min_tls, uint32_t algo_strength, int rule, - int32_t strength_bits, CIPHER_ORDER **head_p, - CIPHER_ORDER **tail_p) + int32_t strength_bits, int in_group, + CIPHER_ORDER **head_p, CIPHER_ORDER **tail_p) { CIPHER_ORDER *head, *tail, *curr, *next, *last; const SSL_CIPHER *cp; @@ -783,9 +789,9 @@ #ifdef CIPHER_DEBUG fprintf(stderr, - "Applying rule %d with %08x/%08x/%08x/%08x/%08x %08x (%d)\n", + "Applying rule %d with %08x/%08x/%08x/%08x/%08x %08x (%d) g:%d\n", rule, alg_mkey, alg_auth, alg_enc, alg_mac, min_tls, - algo_strength, strength_bits); + algo_strength, strength_bits, in_group); #endif if (rule == CIPHER_DEL || rule == CIPHER_BUMP) @@ -862,6 +868,7 @@ if (!curr->active) { ll_append_tail(&head, curr, &tail); curr->active = 1; + curr->in_group = in_group; } } /* Move the added cipher to this location */ @@ -869,6 +876,7 @@ /* reverse == 0 */ if (curr->active) { ll_append_tail(&head, curr, &tail); + curr->in_group = 0; } } else if (rule == CIPHER_DEL) { /* reverse == 1 */ @@ -880,6 +888,7 @@ */ ll_append_head(&head, curr, &tail); curr->active = 0; + curr->in_group = 0; } } else if (rule == CIPHER_BUMP) { if (curr->active) @@ -947,8 +956,8 @@ */ for (i = max_strength_bits; i >= 0; i--) if (number_uses[i] > 0) - ssl_cipher_apply_rule(0, 0, 0, 0, 0, 0, 0, CIPHER_ORD, i, head_p, - tail_p); + ssl_cipher_apply_rule(0, 0, 0, 0, 0, 0, 0, CIPHER_ORD, i, 0, + head_p, tail_p); OPENSSL_free(number_uses); return 1; @@ -962,7 +971,7 @@ uint32_t alg_mkey, alg_auth, alg_enc, alg_mac, algo_strength; int min_tls; const char *l, *buf; - int j, multi, found, rule, retval, ok, buflen; + int j, multi, found, rule, retval, ok, buflen, in_group = 0, has_group = 0; uint32_t cipher_id = 0; char ch; @@ -973,18 +982,66 @@ if (ch == '\0') break; /* done */ - if (ch == '-') { + if (in_group) { + if (ch == ']') { + if (!in_group) { + SSLerr(SSL_F_SSL_CIPHER_PROCESS_RULESTR, + SSL_R_UNEXPECTED_GROUP_CLOSE); + retval = found = in_group = 0; + break; + } + if (*tail_p) + (*tail_p)->in_group = 0; + in_group = 0; + l++; + continue; + } + if (ch == '|') { + rule = CIPHER_ADD; + l++; + continue; + } else if (!(ch >= 'a' && ch <= 'z') + && !(ch >= 'A' && ch <= 'Z') + && !(ch >= '0' && ch <= '9')) { + SSLerr(SSL_F_SSL_CIPHER_PROCESS_RULESTR, + SSL_R_UNEXPECTED_OPERATOR_IN_GROUP); + retval = found = in_group = 0; + break; + } else { + rule = CIPHER_ADD; + } + } else if (ch == '-') { rule = CIPHER_DEL; l++; } else if (ch == '+') { rule = CIPHER_ORD; l++; + } else if (ch == '!' && has_group) { + SSLerr(SSL_F_SSL_CIPHER_PROCESS_RULESTR, + SSL_R_MIXED_SPECIAL_OPERATOR_WITH_GROUPS); + retval = found = in_group = 0; + break; } else if (ch == '!') { rule = CIPHER_KILL; l++; + } else if (ch == '@' && has_group) { + SSLerr(SSL_F_SSL_CIPHER_PROCESS_RULESTR, + SSL_R_MIXED_SPECIAL_OPERATOR_WITH_GROUPS); + retval = found = in_group = 0; + break; } else if (ch == '@') { rule = CIPHER_SPECIAL; l++; + } else if (ch == '[') { + if (in_group) { + SSLerr(SSL_F_SSL_CIPHER_PROCESS_RULESTR, SSL_R_NESTED_GROUP); + retval = found = in_group = 0; + break; + } + in_group = 1; + has_group = 1; + l++; + continue; } else { rule = CIPHER_ADD; } @@ -1009,7 +1066,7 @@ while (((ch >= 'A') && (ch <= 'Z')) || ((ch >= '0') && (ch <= '9')) || ((ch >= 'a') && (ch <= 'z')) || - (ch == '-') || (ch == '.') || (ch == '=')) + (ch == '-') || (ch == '.') || (ch == '=') || (ch == '_')) #else while (isalnum((unsigned char)ch) || (ch == '-') || (ch == '.') || (ch == '=')) @@ -1026,7 +1083,9 @@ * alphanumeric, so we call this an error. */ SSLerr(SSL_F_SSL_CIPHER_PROCESS_RULESTR, SSL_R_INVALID_COMMAND); - return 0; + retval = found = in_group = 0; + l++; + break; } if (rule == CIPHER_SPECIAL) { @@ -1203,8 +1262,8 @@ } else if (found) { ssl_cipher_apply_rule(cipher_id, alg_mkey, alg_auth, alg_enc, alg_mac, - min_tls, algo_strength, rule, -1, head_p, - tail_p); + min_tls, algo_strength, rule, -1, in_group, + head_p, tail_p); } else { while ((*l != '\0') && !ITEM_SEP(*l)) l++; @@ -1213,6 +1272,11 @@ break; /* done */ } + if (in_group) { + SSLerr(SSL_F_SSL_CIPHER_PROCESS_RULESTR, SSL_R_INVALID_COMMAND); + retval = 0; + } + return retval; } @@ -1376,7 +1440,7 @@ int ret = set_ciphersuites(&(ctx->tls13_ciphersuites), str); if (ret && ctx->cipher_list != NULL) - return update_cipher_list(&ctx->cipher_list, &ctx->cipher_list_by_id, + return update_cipher_list(&ctx->cipher_list->ciphers, &ctx->cipher_list_by_id, ctx->tls13_ciphersuites); return ret; @@ -1389,10 +1453,10 @@ if (s->cipher_list == NULL) { if ((cipher_list = SSL_get_ciphers(s)) != NULL) - s->cipher_list = sk_SSL_CIPHER_dup(cipher_list); + s->cipher_list->ciphers = sk_SSL_CIPHER_dup(cipher_list); } if (ret && s->cipher_list != NULL) - return update_cipher_list(&s->cipher_list, &s->cipher_list_by_id, + return update_cipher_list(&s->cipher_list->ciphers, &s->cipher_list_by_id, s->tls13_ciphersuites); return ret; @@ -1400,17 +1464,20 @@ STACK_OF(SSL_CIPHER) *ssl_create_cipher_list(const SSL_METHOD *ssl_method, STACK_OF(SSL_CIPHER) *tls13_ciphersuites, - STACK_OF(SSL_CIPHER) **cipher_list, + struct ssl_cipher_preference_list_st **cipher_list, STACK_OF(SSL_CIPHER) **cipher_list_by_id, const char *rule_str, CERT *c) { - int ok, num_of_ciphers, num_of_alias_max, num_of_group_aliases, i; + int ok, num_of_ciphers, num_of_alias_max, num_of_group_aliases; uint32_t disabled_mkey, disabled_auth, disabled_enc, disabled_mac; - STACK_OF(SSL_CIPHER) *cipherstack; + STACK_OF(SSL_CIPHER) *cipherstack = NULL; const char *rule_p; CIPHER_ORDER *co_list = NULL, *head = NULL, *tail = NULL, *curr; const SSL_CIPHER **ca_list = NULL; + uint8_t *in_group_flags = NULL; + unsigned int num_in_group_flags = 0; + struct ssl_cipher_preference_list_st *pref_list = NULL; /* * Return with error if nothing to do. @@ -1459,16 +1526,16 @@ * preference). */ ssl_cipher_apply_rule(0, SSL_kECDHE, SSL_aECDSA, 0, 0, 0, 0, CIPHER_ADD, - -1, &head, &tail); - ssl_cipher_apply_rule(0, SSL_kECDHE, 0, 0, 0, 0, 0, CIPHER_ADD, -1, &head, - &tail); - ssl_cipher_apply_rule(0, SSL_kECDHE, 0, 0, 0, 0, 0, CIPHER_DEL, -1, &head, - &tail); + -1, 0, &head, &tail); + ssl_cipher_apply_rule(0, SSL_kECDHE, 0, 0, 0, 0, 0, CIPHER_ADD, -1, 0, + &head, &tail); + ssl_cipher_apply_rule(0, SSL_kECDHE, 0, 0, 0, 0, 0, CIPHER_DEL, -1, 0, + &head, &tail); /* Within each strength group, we prefer GCM over CHACHA... */ - ssl_cipher_apply_rule(0, 0, 0, SSL_AESGCM, 0, 0, 0, CIPHER_ADD, -1, + ssl_cipher_apply_rule(0, 0, 0, SSL_AESGCM, 0, 0, 0, CIPHER_ADD, -1, 0, &head, &tail); - ssl_cipher_apply_rule(0, 0, 0, SSL_CHACHA20, 0, 0, 0, CIPHER_ADD, -1, + ssl_cipher_apply_rule(0, 0, 0, SSL_CHACHA20, 0, 0, 0, CIPHER_ADD, -1, 0, &head, &tail); /* @@ -1477,13 +1544,13 @@ * strength. */ ssl_cipher_apply_rule(0, 0, 0, SSL_AES ^ SSL_AESGCM, 0, 0, 0, CIPHER_ADD, - -1, &head, &tail); + -1, 0, &head, &tail); /* Temporarily enable everything else for sorting */ - ssl_cipher_apply_rule(0, 0, 0, 0, 0, 0, 0, CIPHER_ADD, -1, &head, &tail); + ssl_cipher_apply_rule(0, 0, 0, 0, 0, 0, 0, CIPHER_ADD, -1, 0, &head, &tail); /* Low priority for MD5 */ - ssl_cipher_apply_rule(0, 0, 0, 0, SSL_MD5, 0, 0, CIPHER_ORD, -1, &head, + ssl_cipher_apply_rule(0, 0, 0, 0, SSL_MD5, 0, 0, CIPHER_ORD, -1, 0, &head, &tail); /* @@ -1491,16 +1558,16 @@ * disabled. (For applications that allow them, they aren't too bad, but * we prefer authenticated ciphers.) */ - ssl_cipher_apply_rule(0, 0, SSL_aNULL, 0, 0, 0, 0, CIPHER_ORD, -1, &head, + ssl_cipher_apply_rule(0, 0, SSL_aNULL, 0, 0, 0, 0, CIPHER_ORD, -1, 0, &head, &tail); - ssl_cipher_apply_rule(0, SSL_kRSA, 0, 0, 0, 0, 0, CIPHER_ORD, -1, &head, + ssl_cipher_apply_rule(0, SSL_kRSA, 0, 0, 0, 0, 0, CIPHER_ORD, -1, 0, &head, &tail); - ssl_cipher_apply_rule(0, SSL_kPSK, 0, 0, 0, 0, 0, CIPHER_ORD, -1, &head, + ssl_cipher_apply_rule(0, SSL_kPSK, 0, 0, 0, 0, 0, CIPHER_ORD, -1, 0, &head, &tail); /* RC4 is sort-of broken -- move to the end */ - ssl_cipher_apply_rule(0, 0, 0, SSL_RC4, 0, 0, 0, CIPHER_ORD, -1, &head, + ssl_cipher_apply_rule(0, 0, 0, SSL_RC4, 0, 0, 0, CIPHER_ORD, -1, 0, &head, &tail); /* @@ -1516,7 +1583,7 @@ * Partially overrule strength sort to prefer TLS 1.2 ciphers/PRFs. * TODO(openssl-team): is there an easier way to accomplish all this? */ - ssl_cipher_apply_rule(0, 0, 0, 0, 0, TLS1_2_VERSION, 0, CIPHER_BUMP, -1, + ssl_cipher_apply_rule(0, 0, 0, 0, 0, TLS1_2_VERSION, 0, CIPHER_BUMP, -1, 0, &head, &tail); /* @@ -1532,15 +1599,18 @@ * Because we now bump ciphers to the top of the list, we proceed in * reverse order of preference. */ - ssl_cipher_apply_rule(0, 0, 0, 0, SSL_AEAD, 0, 0, CIPHER_BUMP, -1, + ssl_cipher_apply_rule(0, 0, 0, 0, SSL_AEAD, 0, 0, CIPHER_BUMP, -1, 0, &head, &tail); ssl_cipher_apply_rule(0, SSL_kDHE | SSL_kECDHE, 0, 0, 0, 0, 0, - CIPHER_BUMP, -1, &head, &tail); + CIPHER_BUMP, -1, 0, &head, &tail); ssl_cipher_apply_rule(0, SSL_kDHE | SSL_kECDHE, 0, 0, SSL_AEAD, 0, 0, - CIPHER_BUMP, -1, &head, &tail); + CIPHER_BUMP, -1, 0, &head, &tail); + + ssl_cipher_apply_rule(0, 0, 0, 0, 0, TLS1_3_VERSION, 0, CIPHER_BUMP, -1, 0, + &head, &tail); /* Now disable everything (maintaining the ordering!) */ - ssl_cipher_apply_rule(0, 0, 0, 0, 0, 0, 0, CIPHER_DEL, -1, &head, &tail); + ssl_cipher_apply_rule(0, 0, 0, 0, 0, 0, 0, CIPHER_DEL, -1, 0, &head, &tail); /* * We also need cipher aliases for selecting based on the rule_str. @@ -1554,9 +1624,8 @@ num_of_alias_max = num_of_ciphers + num_of_group_aliases + 1; ca_list = OPENSSL_malloc(sizeof(*ca_list) * num_of_alias_max); if (ca_list == NULL) { - OPENSSL_free(co_list); SSLerr(SSL_F_SSL_CREATE_CIPHER_LIST, ERR_R_MALLOC_FAILURE); - return NULL; /* Failure */ + goto err; /* Failure */ } ssl_cipher_collect_aliases(ca_list, num_of_group_aliases, disabled_mkey, disabled_auth, disabled_enc, @@ -1581,29 +1650,19 @@ OPENSSL_free(ca_list); /* Not needed anymore */ - if (!ok) { /* Rule processing failure */ - OPENSSL_free(co_list); - return NULL; - } + if (!ok) + goto err; /* Rule processing failure */ /* * Allocate new "cipherstack" for the result, return with error * if we cannot get one. */ - if ((cipherstack = sk_SSL_CIPHER_new_null()) == NULL) { - OPENSSL_free(co_list); - return NULL; - } + if ((cipherstack = sk_SSL_CIPHER_new_null()) == NULL) + goto err; - /* Add TLSv1.3 ciphers first - we always prefer those if possible */ - for (i = 0; i < sk_SSL_CIPHER_num(tls13_ciphersuites); i++) { - if (!sk_SSL_CIPHER_push(cipherstack, - sk_SSL_CIPHER_value(tls13_ciphersuites, i))) { - OPENSSL_free(co_list); - sk_SSL_CIPHER_free(cipherstack); - return NULL; - } - } + in_group_flags = OPENSSL_malloc(num_of_ciphers); + if (!in_group_flags) + goto err; /* * The cipher selection for the list is done. The ciphers are added @@ -1611,26 +1670,50 @@ */ for (curr = head; curr != NULL; curr = curr->next) { if (curr->active) { - if (!sk_SSL_CIPHER_push(cipherstack, curr->cipher)) { - OPENSSL_free(co_list); - sk_SSL_CIPHER_free(cipherstack); - return NULL; - } + if (!sk_SSL_CIPHER_push(cipherstack, curr->cipher)) + goto err; + in_group_flags[num_in_group_flags++] = curr->in_group; #ifdef CIPHER_DEBUG fprintf(stderr, "<%s>\n", curr->cipher->name); #endif } } + OPENSSL_free(co_list); /* Not needed any longer */ + co_list = NULL; - if (!update_cipher_list_by_id(cipher_list_by_id, cipherstack)) { - sk_SSL_CIPHER_free(cipherstack); - return NULL; - } - sk_SSL_CIPHER_free(*cipher_list); - *cipher_list = cipherstack; + if (!update_cipher_list_by_id(cipher_list_by_id, cipherstack)) + goto err; + + pref_list = OPENSSL_malloc(sizeof(struct ssl_cipher_preference_list_st)); + if (!pref_list) + goto err; + pref_list->ciphers = cipherstack; + pref_list->in_group_flags = OPENSSL_malloc(num_in_group_flags); + if (!pref_list->in_group_flags) + goto err; + memcpy(pref_list->in_group_flags, in_group_flags, num_in_group_flags); + OPENSSL_free(in_group_flags); + in_group_flags = NULL; + if (*cipher_list != NULL) + ssl_cipher_preference_list_free(*cipher_list); + *cipher_list = pref_list; + pref_list = NULL; return cipherstack; + +err: + if (co_list) + OPENSSL_free(co_list); + if (in_group_flags) + OPENSSL_free(in_group_flags); + if (cipherstack) + sk_SSL_CIPHER_free(cipherstack); + if (pref_list && pref_list->in_group_flags) + OPENSSL_free(pref_list->in_group_flags); + if (pref_list) + OPENSSL_free(pref_list); + return NULL; } char *SSL_CIPHER_description(const SSL_CIPHER *cipher, char *buf, int len) @@ -1791,6 +1874,9 @@ case SSL_CHACHA20POLY1305: enc = "CHACHA20/POLY1305(256)"; break; + case SSL_CHACHA20POLY1305_D: + enc = "CHACHA20/POLY1305-Draft(256)"; + break; default: enc = "unknown"; break; @@ -2115,7 +2201,7 @@ out = EVP_CCM_TLS_EXPLICIT_IV_LEN + 16; } else if (c->algorithm_enc & (SSL_AES128CCM8 | SSL_AES256CCM8)) { out = EVP_CCM_TLS_EXPLICIT_IV_LEN + 8; - } else if (c->algorithm_enc & SSL_CHACHA20POLY1305) { + } else if (c->algorithm_enc & (SSL_CHACHA20POLY1305 | SSL_CHACHA20POLY1305_D)) { out = 16; } else if (c->algorithm_mac & SSL_AEAD) { /* We're supposed to have handled all the AEAD modes above */ diff --color -uNr a/ssl/ssl_err.c b/ssl/ssl_err.c --- a/ssl/ssl_err.c 2022-11-01 20:36:10.000000000 +0800 +++ b/ssl/ssl_err.c 2022-12-25 15:28:59.873389966 +0800 @@ -968,6 +968,9 @@ {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_MISSING_TMP_DH_KEY), "missing tmp dh key"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_MISSING_TMP_ECDH_KEY), "missing tmp ecdh key"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_MIXED_SPECIAL_OPERATOR_WITH_GROUPS), + "mixed special operator with groups"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_NESTED_GROUP), "nested group"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_MIXED_HANDSHAKE_AND_NON_HANDSHAKE_DATA), "mixed handshake and non handshake data"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_NOT_ON_RECORD_BOUNDARY), @@ -1206,11 +1209,14 @@ "unable to load ssl3 md5 routines"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_UNABLE_TO_LOAD_SSL3_SHA1_ROUTINES), "unable to load ssl3 sha1 routines"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_UNEXPECTED_GROUP_CLOSE), "unexpected group close"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_UNEXPECTED_CCS_MESSAGE), "unexpected ccs message"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_UNEXPECTED_END_OF_EARLY_DATA), "unexpected end of early data"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_UNEXPECTED_MESSAGE), "unexpected message"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_UNEXPECTED_OPERATOR_IN_GROUP), + "unexpected operator in group"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_UNEXPECTED_RECORD), "unexpected record"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_UNINITIALIZED), "uninitialized"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_UNKNOWN_ALERT_TYPE), "unknown alert type"}, diff --color -uNr a/ssl/ssl_lib.c b/ssl/ssl_lib.c --- a/ssl/ssl_lib.c 2022-11-01 20:36:10.000000000 +0800 +++ b/ssl/ssl_lib.c 2022-12-25 15:28:59.875390018 +0800 @@ -1128,6 +1128,71 @@ return X509_VERIFY_PARAM_set1(ssl->param, vpm); } +void ssl_cipher_preference_list_free(struct ssl_cipher_preference_list_st + *cipher_list) +{ + sk_SSL_CIPHER_free(cipher_list->ciphers); + OPENSSL_free(cipher_list->in_group_flags); + OPENSSL_free(cipher_list); +} + +struct ssl_cipher_preference_list_st* +ssl_cipher_preference_list_dup(struct ssl_cipher_preference_list_st + *cipher_list) +{ + struct ssl_cipher_preference_list_st* ret = NULL; + size_t n = sk_SSL_CIPHER_num(cipher_list->ciphers); + + ret = OPENSSL_malloc(sizeof(struct ssl_cipher_preference_list_st)); + if (!ret) + goto err; + ret->ciphers = NULL; + ret->in_group_flags = NULL; + ret->ciphers = sk_SSL_CIPHER_dup(cipher_list->ciphers); + if (!ret->ciphers) + goto err; + ret->in_group_flags = OPENSSL_malloc(n); + if (!ret->in_group_flags) + goto err; + memcpy(ret->in_group_flags, cipher_list->in_group_flags, n); + return ret; + +err: + if (ret->ciphers) + sk_SSL_CIPHER_free(ret->ciphers); + if (ret) + OPENSSL_free(ret); + return NULL; +} + +struct ssl_cipher_preference_list_st* +ssl_cipher_preference_list_from_ciphers(STACK_OF(SSL_CIPHER) *ciphers) +{ + struct ssl_cipher_preference_list_st* ret = NULL; + size_t n = sk_SSL_CIPHER_num(ciphers); + + ret = OPENSSL_malloc(sizeof(struct ssl_cipher_preference_list_st)); + if (!ret) + goto err; + ret->ciphers = NULL; + ret->in_group_flags = NULL; + ret->ciphers = sk_SSL_CIPHER_dup(ciphers); + if (!ret->ciphers) + goto err; + ret->in_group_flags = OPENSSL_malloc(n); + if (!ret->in_group_flags) + goto err; + memset(ret->in_group_flags, 0, n); + return ret; + +err: + if (ret->ciphers) + sk_SSL_CIPHER_free(ret->ciphers); + if (ret) + OPENSSL_free(ret); + return NULL; +} + X509_VERIFY_PARAM *SSL_CTX_get0_param(SSL_CTX *ctx) { return ctx->param; @@ -1168,7 +1233,8 @@ BUF_MEM_free(s->init_buf); /* add extra stuff */ - sk_SSL_CIPHER_free(s->cipher_list); + if (s->cipher_list != NULL) + ssl_cipher_preference_list_free(s->cipher_list); sk_SSL_CIPHER_free(s->cipher_list_by_id); sk_SSL_CIPHER_free(s->tls13_ciphersuites); sk_SSL_CIPHER_free(s->peer_ciphers); @@ -2466,9 +2532,9 @@ { if (s != NULL) { if (s->cipher_list != NULL) { - return s->cipher_list; + return (s->cipher_list->ciphers); } else if ((s->ctx != NULL) && (s->ctx->cipher_list != NULL)) { - return s->ctx->cipher_list; + return (s->ctx->cipher_list->ciphers); } } return NULL; @@ -2542,8 +2608,8 @@ * preference */ STACK_OF(SSL_CIPHER) *SSL_CTX_get_ciphers(const SSL_CTX *ctx) { - if (ctx != NULL) - return ctx->cipher_list; + if (ctx != NULL && ctx->cipher_list != NULL) + return ctx->cipher_list->ciphers; return NULL; } @@ -3087,7 +3153,7 @@ ret->tls13_ciphersuites, &ret->cipher_list, &ret->cipher_list_by_id, SSL_DEFAULT_CIPHER_LIST, ret->cert) - || sk_SSL_CIPHER_num(ret->cipher_list) <= 0) { + || sk_SSL_CIPHER_num(ret->cipher_list->ciphers) <= 0) { SSLerr(SSL_F_SSL_CTX_NEW, SSL_R_LIBRARY_HAS_NO_CIPHERS); goto err2; } @@ -3263,7 +3329,7 @@ #ifndef OPENSSL_NO_CT CTLOG_STORE_free(a->ctlog_store); #endif - sk_SSL_CIPHER_free(a->cipher_list); + ssl_cipher_preference_list_free(a->cipher_list); sk_SSL_CIPHER_free(a->cipher_list_by_id); sk_SSL_CIPHER_free(a->tls13_ciphersuites); ssl_cert_free(a->cert); @@ -3929,13 +3995,15 @@ /* dup the cipher_list and cipher_list_by_id stacks */ if (s->cipher_list != NULL) { - if ((ret->cipher_list = sk_SSL_CIPHER_dup(s->cipher_list)) == NULL) + ret->cipher_list = ssl_cipher_preference_list_dup(s->cipher_list); + if (ret->cipher_list == NULL) goto err; } - if (s->cipher_list_by_id != NULL) - if ((ret->cipher_list_by_id = sk_SSL_CIPHER_dup(s->cipher_list_by_id)) - == NULL) + if (s->cipher_list_by_id != NULL) { + ret->cipher_list_by_id = sk_SSL_CIPHER_dup(s->cipher_list_by_id); + if (ret->cipher_list_by_id == NULL) goto err; + } /* Dup the client_CA list */ if (!dup_ca_names(&ret->ca_names, s->ca_names) diff --color -uNr a/ssl/ssl_local.h b/ssl/ssl_local.h --- a/ssl/ssl_local.h 2022-11-01 20:36:10.000000000 +0800 +++ b/ssl/ssl_local.h 2022-12-25 15:28:59.876390044 +0800 @@ -230,12 +230,13 @@ # define SSL_CHACHA20POLY1305 0x00080000U # define SSL_ARIA128GCM 0x00100000U # define SSL_ARIA256GCM 0x00200000U +# define SSL_CHACHA20POLY1305_D 0x00400000U # define SSL_AESGCM (SSL_AES128GCM | SSL_AES256GCM) # define SSL_AESCCM (SSL_AES128CCM | SSL_AES256CCM | SSL_AES128CCM8 | SSL_AES256CCM8) # define SSL_AES (SSL_AES128|SSL_AES256|SSL_AESGCM|SSL_AESCCM) # define SSL_CAMELLIA (SSL_CAMELLIA128|SSL_CAMELLIA256) -# define SSL_CHACHA20 (SSL_CHACHA20POLY1305) +# define SSL_CHACHA20 (SSL_CHACHA20POLY1305 | SSL_CHACHA20POLY1305_D) # define SSL_ARIAGCM (SSL_ARIA128GCM | SSL_ARIA256GCM) # define SSL_ARIA (SSL_ARIAGCM) @@ -732,9 +733,46 @@ unsigned char tick_aes_key[TLSEXT_TICK_KEY_LENGTH]; } SSL_CTX_EXT_SECURE; +/* ssl_cipher_preference_list_st contains a list of SSL_CIPHERs with + * equal-preference groups. For TLS clients, the groups are moot because the + * server picks the cipher and groups cannot be expressed on the wire. However, + * for servers, the equal-preference groups allow the client's preferences to + * be partially respected. (This only has an effect with + * SSL_OP_CIPHER_SERVER_PREFERENCE). + * + * The equal-preference groups are expressed by grouping SSL_CIPHERs together. + * All elements of a group have the same priority: no ordering is expressed + * within a group. + * + * The values in |ciphers| are in one-to-one correspondence with + * |in_group_flags|. (That is, sk_SSL_CIPHER_num(ciphers) is the number of + * bytes in |in_group_flags|.) The bytes in |in_group_flags| are either 1, to + * indicate that the corresponding SSL_CIPHER is not the last element of a + * group, or 0 to indicate that it is. + * + * For example, if |in_group_flags| contains all zeros then that indicates a + * traditional, fully-ordered preference. Every SSL_CIPHER is the last element + * of the group (i.e. they are all in a one-element group). + * + * For a more complex example, consider: + * ciphers: A B C D E F + * in_group_flags: 1 1 0 0 1 0 + * + * That would express the following, order: + * + * A E + * B -> D -> F + * C + */ +struct ssl_cipher_preference_list_st { + STACK_OF(SSL_CIPHER) *ciphers; + uint8_t *in_group_flags; +}; + + struct ssl_ctx_st { const SSL_METHOD *method; - STACK_OF(SSL_CIPHER) *cipher_list; + struct ssl_cipher_preference_list_st *cipher_list; /* same as above but sorted for lookup */ STACK_OF(SSL_CIPHER) *cipher_list_by_id; /* TLSv1.3 specific ciphersuites */ @@ -1130,7 +1168,7 @@ SSL_DANE dane; /* crypto */ STACK_OF(SSL_CIPHER) *peer_ciphers; - STACK_OF(SSL_CIPHER) *cipher_list; + struct ssl_cipher_preference_list_st *cipher_list; STACK_OF(SSL_CIPHER) *cipher_list_by_id; /* TLSv1.3 specific ciphersuites */ STACK_OF(SSL_CIPHER) *tls13_ciphersuites; @@ -2268,7 +2306,7 @@ const SSL_CIPHER *const *bp); __owur STACK_OF(SSL_CIPHER) *ssl_create_cipher_list(const SSL_METHOD *ssl_method, STACK_OF(SSL_CIPHER) *tls13_ciphersuites, - STACK_OF(SSL_CIPHER) **cipher_list, + struct ssl_cipher_preference_list_st **cipher_list, STACK_OF(SSL_CIPHER) **cipher_list_by_id, const char *rule_str, CERT *c); @@ -2278,6 +2316,13 @@ STACK_OF(SSL_CIPHER) **scsvs, int sslv2format, int fatal); void ssl_update_cache(SSL *s, int mode); +struct ssl_cipher_preference_list_st* ssl_cipher_preference_list_dup( + struct ssl_cipher_preference_list_st *cipher_list); +void ssl_cipher_preference_list_free( + struct ssl_cipher_preference_list_st *cipher_list); +struct ssl_cipher_preference_list_st* ssl_cipher_preference_list_from_ciphers( + STACK_OF(SSL_CIPHER) *ciphers); +struct ssl_cipher_preference_list_st* ssl_get_cipher_preferences(SSL *s); __owur int ssl_cipher_get_evp(const SSL_SESSION *s, const EVP_CIPHER **enc, const EVP_MD **md, int *mac_pkey_type, size_t *mac_secret_size, SSL_COMP **comp, @@ -2363,7 +2408,7 @@ CERT_PKEY *cpk); __owur const SSL_CIPHER *ssl3_choose_cipher(SSL *ssl, STACK_OF(SSL_CIPHER) *clnt, - STACK_OF(SSL_CIPHER) *srvr); + struct ssl_cipher_preference_list_st *srvr); __owur int ssl3_digest_cached_records(SSL *s, int keep); __owur int ssl3_new(SSL *s); void ssl3_free(SSL *s); diff --color -uNr a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c --- a/ssl/statem/statem_srvr.c 2022-11-01 20:36:10.000000000 +0800 +++ b/ssl/statem/statem_srvr.c 2022-12-25 15:28:59.877390070 +0800 @@ -1773,7 +1773,7 @@ /* For TLSv1.3 we must select the ciphersuite *before* session resumption */ if (SSL_IS_TLS13(s)) { const SSL_CIPHER *cipher = - ssl3_choose_cipher(s, ciphers, SSL_get_ciphers(s)); + ssl3_choose_cipher(s, ciphers, ssl_get_cipher_preferences(s)); if (cipher == NULL) { SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE, @@ -1954,7 +1954,7 @@ /* check if some cipher was preferred by call back */ if (pref_cipher == NULL) pref_cipher = ssl3_choose_cipher(s, s->peer_ciphers, - SSL_get_ciphers(s)); + ssl_get_cipher_preferences(s)); if (pref_cipher == NULL) { SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE, SSL_F_TLS_EARLY_POST_PROCESS_CLIENT_HELLO, @@ -1963,8 +1963,9 @@ } s->session->cipher = pref_cipher; - sk_SSL_CIPHER_free(s->cipher_list); - s->cipher_list = sk_SSL_CIPHER_dup(s->peer_ciphers); + ssl_cipher_preference_list_free(s->cipher_list); + s->cipher_list = ssl_cipher_preference_list_from_ciphers( + s->peer_ciphers); sk_SSL_CIPHER_free(s->cipher_list_by_id); s->cipher_list_by_id = sk_SSL_CIPHER_dup(s->peer_ciphers); } @@ -2277,7 +2278,7 @@ /* In TLSv1.3 we selected the ciphersuite before resumption */ if (!SSL_IS_TLS13(s)) { cipher = - ssl3_choose_cipher(s, s->peer_ciphers, SSL_get_ciphers(s)); + ssl3_choose_cipher(s, s->peer_ciphers, ssl_get_cipher_preferences(s)); if (cipher == NULL) { SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE, diff --color -uNr a/util/libcrypto.num b/util/libcrypto.num --- a/util/libcrypto.num 2022-11-01 20:36:10.000000000 +0800 +++ b/util/libcrypto.num 2022-12-25 15:28:59.879390122 +0800 @@ -4591,3 +4591,4 @@ X509_REQ_set0_signature 4545 1_1_1h EXIST::FUNCTION: X509_REQ_set1_signature_algo 4546 1_1_1h EXIST::FUNCTION: EC_KEY_decoded_from_explicit_params 4547 1_1_1h EXIST::FUNCTION:EC +EVP_chacha20_poly1305_draft 4548 1_1_1h EXIST::FUNCTION:CHACHA,POLY1305 ================================================ FILE: rimworld.mods ================================================ Version: 0 Mods: - Id: brrainz.harmony Name: Harmony SteamWorkshopId: 2009463077 - Id: ludeon.rimworld Name: 核心 SteamWorkshopId: 0 - Id: fluffy.modmanager Name: Mod Manager SteamWorkshopId: 1507748539 - Id: imranfish.xmlextensions Name: XML Extensions SteamWorkshopId: 2574315206 - Id: bs.betterlog Name: Better Log - Fix your errors with style SteamWorkshopId: 2772559481 - Id: ludeon.rimworld.royalty Name: 皇权 SteamWorkshopId: 0 - Id: ludeon.rimworld.ideology Name: 文化 SteamWorkshopId: 0 - Id: ludeon.rimworld.biotech Name: 生物技术 SteamWorkshopId: 0 - Id: unlimitedhugs.hugslib Name: HugsLib SteamWorkshopId: 818773962 - Id: rwzh.chinesepack.hugslib Name: HugsLib_zh SteamWorkshopId: 1798770746 - Id: superniquito.modoptionssort Name: Mod Options Sort SteamWorkshopId: 2910865748 - Id: arandomkiwi.rimsaves Name: RimSaves SteamWorkshopId: 1713367505 - Id: rwzh.chinesepack.rimsaves Name: RimSaves_zh SteamWorkshopId: 2199383843 - Id: madeline.modmismatchformatter Name: Better ModMismatch Window SteamWorkshopId: 1872244972 - Id: owlchemist.midsaversaver Name: Mid-saver Saver SteamWorkshopId: 2921585989 - Id: chinesepackage.owlchemist.midsaversaver Name: Mid-saver Saver_zh SteamWorkshopId: 2924284353 - Id: kenx00x.xenotypeandideologybuttonstitlescreen Name: Xenotype And Ideology Buttons TitleScreen SteamWorkshopId: 2973951983 - Id: peppsen.pmusic Name: P-Music SteamWorkshopId: 725130005 - Id: fluffy.musicmanager Name: Music Manager SteamWorkshopId: 2229205672 - Id: telefonmast.graphicssettings Name: Graphics Settings+ SteamWorkshopId: 1678847247 - Id: tinygrox.chs.graphicssettings Name: Graphics Settings CHS SteamWorkshopId: 2888729664 - Id: kapitanoczywisty.minimallightcontrol Name: Minimal Light Control SteamWorkshopId: 1554307259 - Id: oskarpotocki.vanillafactionsexpanded.core Name: Vanilla Expanded Framework SteamWorkshopId: 2023507013 - Id: rwzh.chinesepack.vanillafactionsexpandedcore Name: Vanilla_Expanded_Framework_zh SteamWorkshopId: 2024971883 - Id: vanillaexpanded.vanillatraitsexpanded Name: Vanilla Traits Expanded SteamWorkshopId: 2296404655 - Id: rwzh.chinesepack.vanillatraitsexpanded Name: Vanilla_Traits_Expanded_zh SteamWorkshopId: 2301508015 - Id: vanillaexpanded.vtexe Name: Vanilla Textures Expanded SteamWorkshopId: 2016436324 - Id: rwzh.chinesepack.vtexe Name: Vanilla_Textures_Expanded_zh SteamWorkshopId: 2035866176 - Id: vanillaexpanded.vfepower Name: Vanilla Furniture Expanded - Power SteamWorkshopId: 2062943477 - Id: rwzh.chinesepack.vfe Name: Vanilla_Furniture_Expanded_zh_pack SteamWorkshopId: 2033672219 - Id: vanillaexpanded.vcef Name: Vanilla Fishing Expanded SteamWorkshopId: 1914064942 - Id: rwzh.chinesepack.vcef Name: Vanilla_Fishing_Expanded_zh SteamWorkshopId: 2035866747 - Id: tenthwit.apparel Name: Retextured Apparel SteamWorkshopId: 2042613316 - Id: tied.parkaretex Name: Parka Retexture SteamWorkshopId: 2984307648 - Id: tenthwit.sculptures Name: Retextured Sculptures SteamWorkshopId: 2045252395 - Id: owlchemist.bettervanillamasking Name: Better Vanilla Masking SteamWorkshopId: 1736114368 - Id: unofficial.morepracticaltraits Name: More Practical Traits - unofficial update SteamWorkshopId: 2893524668 - Id: moretraitslots.kv.rw Name: '[KV] More Trait Slots [1.4]' SteamWorkshopId: 2878346573 - Id: zh.moretraitslotstaranchuk.only Name: '[KV] More Trait Slots [1.4] -- 更多特性槽1.4 简体汉化包' SteamWorkshopId: 2878778439 - Id: darkborderman.moretraitgenes Name: More Genes SteamWorkshopId: 2878539467 - Id: zh.moretraitgenes Name: More Genes简体中文汉化包 SteamWorkshopId: 2884401339 - Id: gloomy.face.mk2.unofficial Name: Gloomy Face mk2 Unofficial SteamWorkshopId: 2910048077 - Id: nals.chibifacefemale Name: '[NL] Chibi Face Female only' SteamWorkshopId: 1540475285 - Id: hourai.vanillahairsretex Name: Vanilla Hairs Retextured SteamWorkshopId: 2893572291 - Id: klr.gloomyhairmk2forked Name: Gloomy Hair mk2 FORKED[1.4] SteamWorkshopId: 2895002564 - Id: jintuzilamian.hairstyle.afumenshairstyles Name: AFU男士发型_Men's hairstyles SteamWorkshopId: 2072106893 - Id: jintuzilamian.hairstyle.afuwomenshairstyles Name: AFU女士发型_Women's hairstyles SteamWorkshopId: 2020779666 - Id: nera.spawnthosegenes Name: '[BT] SpawnThoseGenes!' SteamWorkshopId: 2898044088 - Id: nals.facialanimation Name: '[NL] Facial Animation - WIP' SteamWorkshopId: 1635901197 - Id: oppey.eyegenes2 Name: '[BT] EyeGenes2 | Base - [NL] Facial Animation |' SteamWorkshopId: 2898151329 - Id: oppey.goawayeyestrain Name: Go Away Eye Strain! SteamWorkshopId: 2895295315 - Id: atom.tree Name: Anima and Gauranlen Tree Retexture SteamWorkshopId: 2662326639 - Id: agentblac.makepawnsprisoners Name: 1.4 - Imprisonment On The Go! (Make Pawns Prisoners Without Beds) SteamWorkshopId: 1203903378 - Id: wrk.woundremover Name: 1.3/Ideology Wound Remover SteamWorkshopId: 2839444849 - Id: edb.preparecarefully Name: EdB Prepare Carefully SteamWorkshopId: 735106432 - Id: void.charactereditor Name: Character Editor SteamWorkshopId: 1874644848 - Id: boundir.newgameplus Name: New Game Plus SteamWorkshopId: 2909126210 - Id: zh.boundir.newgameplus Name: New Game Plus_zh SteamWorkshopId: 2921894258 - Id: sarg.smartspeed Name: Smart Speed SteamWorkshopId: 1504723424 - Id: zylle.mapdesigner Name: Map Designer SteamWorkshopId: 2111424996 - Id: rwzh.chinesepack.mapdesigner Name: Map_Designer_zh SteamWorkshopId: 2115722898 - Id: m00nl1ght.mappreview Name: Map Preview SteamWorkshopId: 2800857642 - Id: zh.m00nl1ght.mappreview Name: Map Preview_zh SteamWorkshopId: 2804421885 - Id: m00nl1ght.geologicallandforms Name: Geological Landforms SteamWorkshopId: 2773943594 - Id: zh.m00nl1ght.geologicallandforms Name: Geological Landforms_zh SteamWorkshopId: 2774968010 - Id: thereallemon.mapreroll Name: Map Reroll (1.4) SteamWorkshopId: 2915575236 - Id: mlie.savemaps Name: Save Maps (Continued) SteamWorkshopId: 2916523481 - Id: murmur.threatpointcap Name: Threat Point Cap SteamWorkshopId: 2380491067 - Id: dark.jobinbar Name: Job In Bar SteamWorkshopId: 2086300611 - Id: chinesetranslation.whw.jobinbar Name: Job In Bar 个人简中汉化 SteamWorkshopId: 2625438810 - Id: derekbickley.ltocolonygroupsfinal Name: '[LTO] Colony Groups' SteamWorkshopId: 2345493945 - Id: zh.ltocolonygroupsfinal Name: '[LTO] Colony Groups简繁中文汉化包' SteamWorkshopId: 2364514563 - Id: dubwise.dubsmintminimap Name: Dubs Mint Minimap SteamWorkshopId: 1662119905 - Id: owlchemist.powertab Name: Power Tab 2 SteamWorkshopId: 2952716728 - Id: hanami.chinesepack.owlchemist.powertab Name: Power Tab 2个人汉化 SteamWorkshopId: 2972581353 - Id: jaxe.bubbles Name: Interaction Bubbles SteamWorkshopId: 1516158345 - Id: jaxe.bubbles.zh.hc Name: Interaction Bubbles_zh SteamWorkshopId: 2707586104 - Id: jaxe.rimhud Name: RimHUD SteamWorkshopId: 1508850027 - Id: shouer.chinesepack.rimhud Name: RimHUD_zh SteamWorkshopId: 1928161832 - Id: creeper.betterinfocard Name: BetterInfoCard SteamWorkshopId: 2890920739 - Id: kahdeg.killfeed Name: Killfeed SteamWorkshopId: 1362098265 - Id: dubwise.dubsappareltweaks Name: Dubs Apparel Tweaks SteamWorkshopId: 2296697286 - Id: rwzh.leafzxg.dubsappareltweaks Name: Dubs_Apparel_Tweaks_zh SteamWorkshopId: 2327363931 - Id: avilmask.commonsense Name: Common Sense SteamWorkshopId: 1561769193 - Id: dingo.lessrebuff Name: Less Rebuff SteamWorkshopId: 774543761 - Id: neronix17.tweaksgalore Name: Tweaks Galore SteamWorkshopId: 2695164414 - Id: mobius.royaltytweaks Name: Royalty Tweaks SteamWorkshopId: 2018384424 - Id: insertkey.buildertweaks Name: BuilderTweaks SteamWorkshopId: 1600554794 - Id: enterprise.changeoperationsuccessratelimit Name: 修改手术成功率上限-Change Operation Success Rate Limit SteamWorkshopId: 2927674331 - Id: brrainz.justignoremepassing Name: Just Ignore Me Passing SteamWorkshopId: 1231617602 - Id: doll.nevergeneraterelations Name: Never Generate Relations SteamWorkshopId: 2891797130 - Id: mlie.allmemoriesfade Name: All Memories Fade SteamWorkshopId: 2800155563 - Id: wemd.handsarenotoutside Name: '[WD] Hands Are Not Outside' SteamWorkshopId: 1985331019 - Id: vesper.notmyfault Name: Not My Fault SteamWorkshopId: 2870045856 - Id: forevermarried.mod Name: "'Til Death Do Them Apart" SteamWorkshopId: 2975068661 - Id: cedaro.nohopelessromance Name: No Hopeless Romance SteamWorkshopId: 2964282061 - Id: amch.needbaroverflow Name: Need Bar Overflow SteamWorkshopId: 2566316158 - Id: bustedbunny.chooseyourrecipe Name: Choose Your Recipe SteamWorkshopId: 2613112847 - Id: dubwise.dubsmintmenus Name: Dubs Mint Menus SteamWorkshopId: 1446523594 - Id: rwzh.chinesepack.dubsmintmenus Name: Dubs_Mint_Menus_zh SteamWorkshopId: 2327364313 - Id: mlie.configurableroomstats Name: Configurable Room Stats (Continued) SteamWorkshopId: 2896467431 - Id: owlchemist.toggleableoverlays Name: Toggleable Overlays SteamWorkshopId: 2608654598 - Id: owlchemist.toggleablereadouts Name: Toggleable Readouts SteamWorkshopId: 2661792499 - Id: zh.owlchemist.toggleableseries Name: Owlchemist - Toggleable Series_zh SteamWorkshopId: 2768513554 - Id: syrus.heatmap Name: Heat Map (Continued) SteamWorkshopId: 2552838384 - Id: legodude17.qualcolor Name: Quality Colors SteamWorkshopId: 2420141361 - Id: qualcolor.zh Name: Quality Colors zh 简体中文汉化 SteamWorkshopId: 2891521968 - Id: owlchemist.perspectiveores Name: 'Perspective: Ores' SteamWorkshopId: 2923871543 - Id: owlchemist.simplefx.vapor Name: 'Simple FX: Vapor' SteamWorkshopId: 2886571275 - Id: owlchemist.simplefx.splashes Name: 'Simple FX: Splashes' SteamWorkshopId: 2889330414 - Id: owlchemist.simplefx.smoke2 Name: 'Simple FX: Smoke' SteamWorkshopId: 2574489704 - Id: owlchemist.scatteredflames Name: Scattered Flames SteamWorkshopId: 2652749788 - Id: owlchemist.scatteredflames.zh.hc Name: Scattered Flames_zh SteamWorkshopId: 2711236044 - Id: telardo.fireworks Name: Fireworks SteamWorkshopId: 2922179297 - Id: fishbones.daylilyretexture Name: I Hate Daylilies SteamWorkshopId: 2878142283 - Id: smashphil.loadinginprogress Name: Loading In Progress SteamWorkshopId: 1975622772 - Id: wiseclock.ac.daysmatter Name: Days Matter (fork) SteamWorkshopId: 2045496988 - Id: telardo.romanceontherim Name: Romance On The Rim SteamWorkshopId: 2654432921 - Id: vanya.tools.blightedalert Name: Blighted Alert SteamWorkshopId: 1494937292 - Id: fyarn.fixablemooddebuffsalert Name: Fixable Mood Debuffs Alert SteamWorkshopId: 1307316672 - Id: tammybee.predatorhuntalert Name: Predator Hunt Alert SteamWorkshopId: 1537786185 - Id: kathanon.repairinthezone Name: Repair in the Zone SteamWorkshopId: 2798537004 - Id: jp.adjacentlight Name: Adjacent Light SteamWorkshopId: 2517853803 - Id: leoltron.maxbuymaxsell Name: Max Buy, Max Sell SteamWorkshopId: 2889812094 - Id: meow.tradablestoneblocks Name: Tradable Stone Blocks SteamWorkshopId: 1522257424 - Id: tac.genetrader Name: Gene Trader SteamWorkshopId: 2886375137 - Id: timmyliang.tradehelper Name: TradeHelper SteamWorkshopId: 2113372560 - Id: grizzlemethis.tradingspot.rw Name: '[GMT] Trading Spot' SteamWorkshopId: 2874517333 - Id: smashphil.dropspot Name: Trade Ships Drop Spot SteamWorkshopId: 1969732297 - Id: joseasoler.tradingoptions Name: Trading Options SteamWorkshopId: 2876541977 - Id: zh.joseasoler.tradingoptions Name: Trading Options-汉化 SteamWorkshopId: 2876724391 - Id: mlie.dismisstrader Name: Dismiss Trader (Continued) SteamWorkshopId: 2078518511 - Id: zephyr.dismisstraderzh Name: Dismiss Trader (Continued)简繁汉化 SteamWorkshopId: 2081531370 - Id: torann.caravanoptions Name: CaravanOptions SteamWorkshopId: 1501729394 - Id: torann.caravanoptions.zh Name: CaravanOptions 简体中文汉化 远行队选项 SteamWorkshopId: 2722022165 - Id: hatena.animaldiscovery Name: Animal Discovery SteamWorkshopId: 2172166576 - Id: merthsoft.designatorshapes Name: Designator Shapes SteamWorkshopId: 1235181370 - Id: ogliss.wanderjoinsplus Name: WanderJoinsPlus SteamWorkshopId: 2177016016 - Id: ogliss.rescueejoinsplus Name: RescueeJoinsPlus SteamWorkshopId: 2177170403 - Id: mlie.incidentpersonstat Name: Incident Person Stat (Continued) SteamWorkshopId: 2896052868 - Id: brrainz.cameraplus Name: Camera+ SteamWorkshopId: 867467808 - Id: rwzh.chinesepack.cameraplus Name: Camera+_zh SteamWorkshopId: 1783453942 - Id: fluffy.followme Name: Follow Me SteamWorkshopId: 715759739 - Id: com.github.alandariva.moreplanning Name: More Planning [1.4] SteamWorkshopId: 2551225702 - Id: unlimitedhugs.allowtool Name: Allow Tool SteamWorkshopId: 761421485 - Id: unlimitedhugs.defensivepositions Name: Defensive Positions SteamWorkshopId: 761219125 - Id: rwzh.chinesepack.unlimitedhugs Name: UnlimitedHugs_zh_pack SteamWorkshopId: 2016725307 - Id: fluffy.blueprints Name: Blueprints SteamWorkshopId: 708455313 - Id: rwzh.leafzxg.blueprints Name: Blueprints_zh SteamWorkshopId: 2905109743 - Id: navyseal5.moreharvestdesignators Name: More Harvest Designators! SteamWorkshopId: 1541250497 - Id: thewirelord354.conduitdeconstruct Name: Conduit Deconstruct SteamWorkshopId: 838336462 - Id: leafzxg.masterofcooking Name: Master of Cooking SteamWorkshopId: 830545304 - Id: leafzxg.masterofcrafting Name: Master of Crafting SteamWorkshopId: 848513545 - Id: mlie.foodpoisoningstackfix Name: Food Poisoning Stack Fix (Continued) SteamWorkshopId: 2843483188 - Id: frozensnowfox.filthvanisheswithrainandtime Name: '[FSF] Filth Vanishes With Rain And Time' SteamWorkshopId: 1508341791 - Id: owlchemist.permeableterrain Name: Permeable Terrain SteamWorkshopId: 2629981384 - Id: oninotamasi.owlchemist.permeableterrain.zh Name: Permeable Terrain繁簡漢化 SteamWorkshopId: 2944952044 - Id: mario1.3patch.metaldontburn Name: Metal Don't Burn (UNOFFICIAL 1.4 PATCH) SteamWorkshopId: 2557145474 - Id: chicken305.efficienlight Name: Chickens Efficient Light SteamWorkshopId: 1498059716 - Id: telardo.dragselect Name: DragSelect SteamWorkshopId: 2599942235 - Id: haecriver.injuredcarry Name: Injured Carry SteamWorkshopId: 2413690575 - Id: alien.milkablemuffalo Name: Milkable Muffalo SteamWorkshopId: 2019852283 - Id: automatic.prisonerbedsetowner Name: Set Owner for Prisoner Beds SteamWorkshopId: 2053931388 - Id: kathanon.dropsome Name: Drop Some SteamWorkshopId: 2823342374 - Id: mlgm.dropsome.zh Name: Drop Some zh 丢弃数量可选 SteamWorkshopId: 2974880246 - Id: kb.dropinventory Name: Drop All Inventory SteamWorkshopId: 2882490442 - Id: mlgm.dropinventory.zh Name: Drop All Inventory_zh一键卸载库存 SteamWorkshopId: 2974881350 - Id: velc.itemlist Name: Pawn's Items List SteamWorkshopId: 1507341718 - Id: owlchemist.doorclearance Name: Door Clearance SteamWorkshopId: 2965544918 - Id: mlie.nomoreautoroof Name: NoMoreAutoroof (Continued) SteamWorkshopId: 2026621806 - Id: legodude17.smartdecon Name: Smarter Deconstruction and Mining SteamWorkshopId: 2398365712 - Id: legodude17.smartdecon.zh Name: Smarter Deconstruction and Mining zh 简体中文 SteamWorkshopId: 2891998257 - Id: dhultgren.smarterconstruction Name: Smarter Construction SteamWorkshopId: 2202185773 - Id: tammybee.cutplantsbeforebuilding Name: Cut plants before building SteamWorkshopId: 1539025677 - Id: fluffy.medicaltab Name: Medical Tab SteamWorkshopId: 715565817 - Id: rwzh.leafzxg.medicaltab Name: Medical_Tab_zh SteamWorkshopId: 2905108685 - Id: fluffy.worktab Name: Work Tab SteamWorkshopId: 725219116 - Id: rwzh.leafzxg.worktab Name: Work_Tab_zh SteamWorkshopId: 2905108789 - Id: voult.betterpawncontrol Name: Better Pawn Control SteamWorkshopId: 1541460369 - Id: jp.perfectpathing Name: Perfect Pathfinding SteamWorkshopId: 2341486509 - Id: telardo.nolaggybed Name: No Laggy Bed SteamWorkshopId: 2790250834 - Id: uuugggg.sharetheload Name: Share The Load SteamWorkshopId: 1356838246 - Id: uuugggg.sharetheload.zh Name: Share The Load zh 简体中文补丁 SteamWorkshopId: 2892578913 - Id: owlchemist.cleanpathfinding Name: Clean Pathfinding 2 SteamWorkshopId: 2603765747 - Id: oninotamasi.owlchemist.cleanpathfinding.zh Name: Clean Pathfinding 2繁簡漢化 SteamWorkshopId: 2947353787 - Id: victor.wallsaresolid Name: Walls are solid SteamWorkshopId: 2896548513 - Id: aelanna.cleaningspeed Name: Cleaning Speed SteamWorkshopId: 2748222814 - Id: zh.aelanna.cleaningspeed Name: Cleaning Speed-汉化 SteamWorkshopId: 2748802839 - Id: seekiworksmod.no11 Name: Chop Wood with Other Skills SteamWorkshopId: 2811154414 - Id: puremj.mjrimmods.vanillafixmortarshellloading Name: 'Vanilla Fix: Mortar Shell Loading' SteamWorkshopId: 2799902757 - Id: puremj.mjrimmods.vanillafixhaulafterslaughter Name: 'Vanilla Fix: Haul After Slaughter' SteamWorkshopId: 2801452324 - Id: puremj.mjrimmods.whileyouarenearby Name: While You Are Nearby SteamWorkshopId: 2784585275 - Id: codeoptimist.jobsofopportunity Name: While You're Up / PUAH+ SteamWorkshopId: 2034960453 - Id: rwzh.chinesepack.jobsofopportunity Name: While_You're_U_PUAH+_zh SteamWorkshopId: 2039718734 - Id: mlie.pawneducation Name: Pawn Education (Continued) SteamWorkshopId: 2296533470 - Id: rwzh.chinesepack.pawneducation Name: Pawn_Education_zh SteamWorkshopId: 2380908925 - Id: fed1splay.pawntargetfix Name: PawnTargetFix SteamWorkshopId: 2014789938 - Id: brrainz.achtung Name: Achtung! SteamWorkshopId: 730936602 - Id: rwzh.chinesepack.achtung Name: Achtung!_zh SteamWorkshopId: 1782765741 - Id: rh2.bcds.first.aid Name: '[RH2] BCD: First Aid' SteamWorkshopId: 2563152474 - Id: zh.rh2bcdfirstaid Name: '[RH2]BCD:First Aid_zh' SteamWorkshopId: 2566595634 - Id: rh2.cpers.arrest.here Name: '[RH2] CPERS: Arrest Here!' SteamWorkshopId: 2563157350 - Id: zh.rh2cpersarresthere Name: '[RH2]CPERS: Arrest Here!_zh' SteamWorkshopId: 2566596115 - Id: roolo.searchanddestroy Name: Search and Destroy SteamWorkshopId: 1467764609 - Id: mlie.dynamiccarryingcapacity Name: Dynamic Carrying Capacity (Continued) SteamWorkshopId: 2967228692 - Id: vanya.tools.bulkcarrier Name: Bulk Carrier SteamWorkshopId: 1428989232 - Id: mlie.10xcarryingcapacity Name: 10x Carrying Capacity (Continued) SteamWorkshopId: 2019000693 - Id: murmur.isid Name: Infestations Spawn in Darkness SteamWorkshopId: 2017514331 - Id: murmur.fasterprojectiles Name: Faster Projectiles SteamWorkshopId: 2880014259 - Id: wemd.whitepowerarmor Name: '[WD] White Power Armor' SteamWorkshopId: 1122453564 - Id: crocodil.foodpoisoningcures Name: Food Poisoning Cures SteamWorkshopId: 2856335335 - Id: zh.crocodil.foodpoisoningcures Name: Food Poisoning Cures_zh SteamWorkshopId: 2857544882 - Id: eagle0600.dresspatients.1.4 Name: Dress Patients (1.4) SteamWorkshopId: 2877763074 - Id: rwzh.chinesepack.dresspatients Name: Dress_Patients_zh SteamWorkshopId: 2063186733 - Id: kathanon.followthevein Name: Follow the Vein SteamWorkshopId: 2923356196 - Id: gorymoon.closeall Name: CloseAll SteamWorkshopId: 2018479759 - Id: dani.dismissletters Name: '[DN] Auto Dismiss Letters' SteamWorkshopId: 2830610278 - Id: sgc.quests Name: Quest Reward Rebalance SteamWorkshopId: 2312419897 - Id: sandy.rpgstyleinventory.avilmask.revamped Name: RPG Style Inventory Revamped SteamWorkshopId: 2478833213 - Id: lbmaian.begonemessage Name: Begone, Message! SteamWorkshopId: 1951637504 - Id: kathanon.showweapontallies Name: Show Weapon Tallies SteamWorkshopId: 2901520677 - Id: dakkpasserida.animareplant Name: Replantable Anima Trees SteamWorkshopId: 2586765823 - Id: proxyer.anestheticpatch Name: Anesthetic balance patch SteamWorkshopId: 2038485179 - Id: proxyer.steamgeyserbuilding Name: Geyser Deconstruct patch SteamWorkshopId: 2317497710 - Id: ben.automaticnightowl Name: Automatic Night Owl SteamWorkshopId: 2056012179 - Id: dninemfive.qualitycooldown Name: Quality Cooldown SteamWorkshopId: 1543069597 - Id: gm.headsetwhelmet Name: Mechanitor Headset with Helmet SteamWorkshopId: 2881642669 - Id: wvc.sergkart.biotech.mechanoididleoptimization Name: Mechanoid Idle Optimization SteamWorkshopId: 2885792215 - Id: scherub.stonecuttingextended Name: Stonecutting Extended SteamWorkshopId: 2571676542 - Id: lanking.syntheticchips Name: Lanking's Synthetic Chips SteamWorkshopId: 2971197280 - Id: winds.bettermechanoidloot Name: Better Mechanoid Loot (重写) SteamWorkshopId: 2963175073 - Id: kikohi.bettergroundpenetratingscanner Name: Better ground-penetrating scanner SteamWorkshopId: 2809972387 - Id: zh.kikohi.bettergroundpenetratingscanner Name: Better ground-penetrating scanner_zh SteamWorkshopId: 2810420557 - Id: victor.psyultra Name: Psy Ultra SteamWorkshopId: 2879578025 - Id: xrushha.craftablebroadshieldcore Name: 'Craftable uncraftables: Shield Core' SteamWorkshopId: 2198480490 - Id: coldzero.craftableroyaltyweapons Name: Craftable Royalty Weapons (Continued) SteamWorkshopId: 2559489638 - Id: zephyr.craftableroyaltyweaponszh Name: Craftable Royalty Weapons 皇权武器制造简繁汉化 SteamWorkshopId: 2063798513 - Id: hrg164hjo.steriletilepatch Name: Make sterile tile beautiful again SteamWorkshopId: 2895016825 - Id: cuddle.donottakemyguns Name: Don't take my guns SteamWorkshopId: 1869879952 - Id: owlchemist.undergroundpowerconduits Name: Underground Power Conduits SteamWorkshopId: 1735421319 - Id: red.owlchemist.undergroundpowerconduits.zh Name: Underground Power Conduits 简繁汉化包 SteamWorkshopId: 2744717109 - Id: leafzxg.deerextension Name: Deer Extension SteamWorkshopId: 1336473924 - Id: wemd.fastmoisturepumps Name: '[WD] Fast Moisture Pumps' SteamWorkshopId: 1164753788 - Id: murmur.walllight Name: Wall Light SteamWorkshopId: 1423699208 - Id: zh.walllight.only Name: Wall Light -- 壁灯 简体汉化包 SteamWorkshopId: 2826726307 - Id: gt.sam.tilledsoil Name: Tilled Soil SteamWorkshopId: 725747149 - Id: rimfridge.kv.rw Name: RimFridge Updated SteamWorkshopId: 2878183338 - Id: haplo.miscellaneous.training Name: Misc. Training SteamWorkshopId: 717575199 - Id: fluffy.backuppower Name: Backup Power SteamWorkshopId: 2084493662 - Id: dracoix.doormat.r12a Name: Door Mat SteamWorkshopId: 1505423207 - Id: notfood.mendandrecycle Name: MendAndRecycle SteamWorkshopId: 735241897 - Id: vis.staticquality Name: Static Quality SteamWorkshopId: 2801204005 - Id: zh.vis.staticquality Name: Static Quality_zh SteamWorkshopId: 2907151281 - Id: ratys.madskills Name: Mad Skills SteamWorkshopId: 731111514 - Id: hatti.qualitybuilder Name: QualityBuilder SteamWorkshopId: 754637870 - Id: rwzh.chinesepack.qualitybuilder Name: QualityBuilder_zh SteamWorkshopId: 1783553525 - Id: jgh.qualitybuilder Name: QualityBuilder retexture SteamWorkshopId: 2638388641 - Id: vat.epoeforked Name: Expanded Prosthetics and Organ Engineering - Forked SteamWorkshopId: 1949064302 - Id: rwzh.leafzxg.epoeforked Name: Expanded_Prosthetics_and_Organ_Engineering_Forked_zh SteamWorkshopId: 2021158028 - Id: vat.epoeforkedroyalty Name: 'EPOE-Forked: Royalty DLC expansion' SteamWorkshopId: 2008970276 - Id: rwzh.leafzxg.epoeforkedroyalty Name: EPOE-Forked_Royalty_DLC_expansion_zh SteamWorkshopId: 2026231230 - Id: nomadrw.turretvanillaretexturerestyle Name: Turret Vanilla Retexture and Restyle SteamWorkshopId: 2980467701 - Id: mosi.bridgecolor2 Name: 'Bridge Color Tweak 2: Colorful Boogaloo' SteamWorkshopId: 2985505036 - Id: iexist.biotech.morebandwidth Name: Bandwidth Enhancer SteamWorkshopId: 2888505789 - Id: zh.iexist.biotech.morebandwidth Name: Bandwidth Enhancer-汉化 SteamWorkshopId: 2889649472 - Id: inglix.fasterbiosculptingpod Name: Faster Biosculpter Pod SteamWorkshopId: 2576257954 - Id: inglix.fasterbiosculptingpod.zh Name: Faster Biosculpter Pod 简繁中文汉化 更快的塑型仓 SteamWorkshopId: 2745481476 - Id: waether.biosculptingplus Name: BioSculptingPlus SteamWorkshopId: 2577455618 - Id: pzqaks.biosculptingplus.zh Name: BioSculptingPlus 简繁汉化包 SteamWorkshopId: 2619378452 - Id: balistafreak.standalonehotspring Name: Standalone Hot Spring SteamWorkshopId: 2205980094 - Id: rwzh.chinesepack.standalonehotspring Name: Standalone_Hot_Spring_zh SteamWorkshopId: 2210994825 - Id: vanya.agent.housemaidnukos Name: '[L] House Maid Nukos' SteamWorkshopId: 1418683071 - Id: mlie.prisonersshouldfearturrets Name: Prisoners Should Fear Turrets SteamWorkshopId: 2602436826 - Id: bustedbunny.prisonersarenotswines Name: Prisoners Are Not Swines SteamWorkshopId: 2613966365 - Id: jelly.propershotguns Name: '[XND] Proper Shotguns (Continued)' SteamWorkshopId: 2584374906 - Id: propershotguns.llamapatch Name: Llama's Proper Shotgun Patches SteamWorkshopId: 2909790590 - Id: hobtook.mortaraccuracy Name: Mortar Accuracy SteamWorkshopId: 1729446857 - Id: essecoisa.flooredyasto Name: Floored - Yellow and Striped Tiles Only [1.0+] SteamWorkshopId: 1543195051 - Id: doctorstupid.sofa Name: Literally just a sofa SteamWorkshopId: 2894560149 - Id: fluffy.fluffybreakdowns Name: Fluffy Breakdowns SteamWorkshopId: 726244033 - Id: mlie.enterhere Name: Enter Here SteamWorkshopId: 2824117092 - Id: zh.enterhere.only Name: Enter Here -- 客从此进 简繁汉化包 SteamWorkshopId: 2826779697 - Id: bbbbilly.makereinforcedbarrel Name: Make Reinforced Barrel SteamWorkshopId: 2538773133 - Id: frozensnowfox.nodefaultshelfstorage Name: '[FSF] No Default Shelf Storage' SteamWorkshopId: 945085502 - Id: nomaxbills.kv.rw Name: '[KV] No Max Bills' SteamWorkshopId: 1588831229 - Id: falconne.bwm Name: Better Workbench Management SteamWorkshopId: 935982361 - Id: maarx.assignedworkbenches Name: AssignedWorkbenches SteamWorkshopId: 2973942644 - Id: neronix17.embrasures Name: Seamless Embrasures SteamWorkshopId: 2290689449 - Id: darthsergeant.10turrets Name: 1.0 Turrets SteamWorkshopId: 2028662113 - Id: neceros.dws.simpleturrets Name: Simple Turrets SteamWorkshopId: 2559263455 - Id: witek.mechscanhaultocharger Name: Mechs can haul to charger SteamWorkshopId: 2883113037 - Id: porio.tunnulerfix Name: Allow Tunnelers To Drill SteamWorkshopId: 2882915775 - Id: witek.fabricorscanrepairmechs Name: Fabricors can repair mechs SteamWorkshopId: 2886185474 - Id: overpl.drawnspots Name: Drawn Spots SteamWorkshopId: 2917791361 - Id: ratys.rtfuse Name: RT Fuse SteamWorkshopId: 728314182 - Id: ratys.rtsolarflareshield Name: RT Solar Flare Shield SteamWorkshopId: 728315620 - Id: rwzh.chinesepack.ratys Name: RT_zh_pack SteamWorkshopId: 2015185500 - Id: stormknight.outdoorlighting Name: Outdoor Lighting SteamWorkshopId: 1545708330 - Id: chen.outdoorlighting.zh Name: Outdoor Lighting-zh SteamWorkshopId: 2902036198 - Id: gwinnbleidd.mothballedanddeadpawns Name: 'Better GC: Mothballed and World Pawns' SteamWorkshopId: 2982026860 - Id: trinity.runtimegcfixed Name: RuntimeGC [1.4] SteamWorkshopId: 2983911897 - Id: taranchuk.performanceoptimizer Name: Performance Optimizer SteamWorkshopId: 2664723367 - Id: krkr.rocketman Name: RocketMan - Performance Mod SteamWorkshopId: 2479389928 - Id: dubwise.dubsperformanceanalyzer.steam Name: Dubs Performance Analyzer SteamWorkshopId: 2038874626 ================================================ FILE: seewo.fw ================================================ seewo.com cvte.com cvtestatic.com cvtapi.com r302.cc jpush.cn umeng.com ifconfig.me qbox.me qiniu.com qiniup.com qiniu.com.w.kunlunno.com aliyuncs.com en5static.com jikstatic.com xbstatic.com bystatic.com seedpace.com up.qbox.me upload.qiniup.com upload.qiniu.com tinychinacdnweb.qiniu.com.w.kunlunno.com qn-pri-easynote-new.en5static.com ali-pro-pub.jikstatic.com ali-pro-pri.xbstatic.com cos-pro-pub.bystatic.com alert-push.seedpace.com cos-pro-pri.xbstatic.com ali-pro-pri-auth.xbstatic.com ali-backup-pri.oss-cn-hangzhou.aliyuncs.com ali-backup-pub.oss-cn-hangzhou.aliyuncs.com cstore-prot-pub.oss-cn-hangzhou.aliyuncs.com cstore-hls-pub.oss-cn-hangzhou.aliyuncs.com cstore-hls-pri.oss-cn-hangzhou.aliyuncs.com cstore-public.oss-cn-hangzhou.aliyuncs.com cstore-public.oss-cn-hangzhou-internal.aliyuncs.com cstore-private.oss-cn-hangzhou.aliyuncs.com cstore-private.oss-cn-hangzhou-internal.aliyuncs.com tmp-aliyun-pri.oss-cn-hangzhou.aliyuncs.com tmp-aliyun-pub.oss-cn-hangzhou.aliyuncs.com tmp-aliyun-pub.oss-cn-hangzhou-internal.aliyuncs.com ================================================ FILE: trackers.best.cn.list ================================================ https://1337.abcvg.info/announce https://api.ipv4online.uk/announce https://btn-prod.ghostchu-services.top/tracker/announce https://p2p.azu.red/announce https://sparkle.ghostchu-services.top/tracker/announce https://sparkle.ghostchu-services.top/announce https://tr.nyacat.pw/announce https://tracker.cloudit.top/announce https://tracker.gcrenwp.top/announce https://tracker.ipfsscan.io/announce https://tracker.kmzs123.cn/announce https://tracker.leechshield.link/announce https://tracker.lilithraws.org/announce https://tracker.pmman.tech/announce https://tracker.tamersunion.org/announce https://tracker1.520.jp/announce https://trackers.mlsub.net/announce https://tracker-zhuqiy.dgj055.icu/announce https://www.peckservers.com:9443/announce http://[2a04:ac00:1:3dd8::1:2710]:2710/announce http://0d.kebhana.mx/announce http://1337.abcvg.info/announce http://207.241.226.111:6969/announce http://207.241.231.226:6969/announce http://35.227.59.57:2701/announce http://bittorrent-tracker.e-n-c-r-y-p-t.net:1337/announce http://bt.poletracker.org:2710/announce http://bt.sc-ol.com:2710/announce http://bt1.xxxxbt.cc:6969/announce http://btfile.sdo.com:6961/announce http://btracker.top:11451/announce http://buny.uk:6969/announce http://bvarf.tracker.sh:2086/announce http://canardscitrons.nohost.me:6969/announce http://ch3oh.ru:6969/announce http://exodus.desync.com:6969/announce http://finbytes.org/announce.php http://fleira.no:6969/announce http://ftp.pet:6969/announce http://home.yxgz.club:6969/announce http://ipv4announce.sktorrent.eu:6969/announce http://ipv6.rer.lol:6969/announce http://open.acgtracker.com:1096/announce http://open.tracker.cl:1337/announce http://p4p.arenabg.com:1337/announce http://public.tracker.vraphim.com:6969/announce http://retracker.hotplug.ru:2710/announce http://retracker.spark-rostov.ru/announce http://saltwood.top:6969/announce http://seeders-paradise.org/announce http://servandroidkino.ru/announce http://share.dmhy.org/annonuce http://share.hkg-fansub.info/announce.php http://shubt.net:2710/announce http://smurfsoft.com:6969/announce http://sukebei.tracker.wf:8888/announce http://t.jaekr.sh:6969/announce http://t.nyaatracker.com/announce http://t.overflow.biz:6969/announce http://taciturn-shadow.spb.ru:6969/announce http://tr.kxmp.cf/announce http://tr.nyacat.pw/announce http://tr1.aag.moe:2095/announce http://tracker.bittor.pw:1337/announce http://tracker.bt-hash.com/announce http://tracker.bz/announce http://tracker.corpscorp.online/announce http://tracker.dler.com:6969/announce http://tracker.dler.org:6969/announce http://tracker.edkj.club:6969/announce http://tracker.ipv6tracker.org/announce http://tracker.lintk.me:2710/announce http://tracker.moxing.party:6969/announce http://tracker.openbittorrent.com/announce http://tracker.peckservers.com:9000/announce http://tracker.qu.ax:6969/announce http://tracker.renfei.net:8080/announce http://tracker.sbsub.com:2710/announce http://tracker.srv00.com:6969/announce http://tracker.torrentino.com/announce http://tracker.vanitycore.co:6969/announce http://tracker.vraphim.com:6969/announce http://tracker.waaa.moe:6969/announce http://tracker.xiaoduola.xyz:6969/announce http://tracker1.bt.moack.co.kr/announce http://tracker1.itzmx.com:8080/announce http://tracker1.torrentino.com/announce http://tracker2.dler.org/announce http://tracker2.torrentino.com/announce http://tracker3.itzmx.com:6961/announce http://tracker3.torrentino.com/announce http://tracker4.itzmx.com:2710/announce http://tracker4.torrentino.com/announce http://tracker810.xyz:11450/announce http://tracker-zhuqiy.dgj055.icu/announce http://wepzone.net:6969/announce http://www.genesis-sp.org:2710/announce http://www.torrentsnipe.info:2701/announce udp://[2a03:7220:8083:cd00::1]:451/announce udp://[2a04:ac00:1:3dd8::1:2710]:2710/announce udp://[2a0f:e586:f:f::81]:6969/announce udp://109.201.134.183:80/announce udp://185.243.218.213:80/announce udp://186.10.170.97:1337/announce udp://208.83.20.20:6969/announce udp://23.153.248.83:6969/announce udp://23.157.120.14:6969/announce udp://23.168.232.9:1337/announce udp://34.89.91.10:1337/announce udp://34.89.91.10:6969/announce udp://34.94.76.146:6969/announce udp://45.9.60.30:6969/announce udp://52.58.128.163:6969/announce udp://83.102.180.21:80/announce udp://91.216.110.53:451/announce udp://93.158.213.92:1337/announce udp://bt.sc-ol.com:2710/announce udp://coppersurfer.tk:6969/announce udp://open.demonii.com:1337/announce udp://retracker.hotplug.ru:2710/announce udp://tracker.openbittorrent.com:80/announce ================================================ FILE: trackers.list ================================================ https://1337.abcvg.info/announce https://1337.abcvg.info:443/announce https://abir0dev.github.io/announce https://api.ipv4online.uk:443/announce https://btn-prod.ghostchu-services.top/tracker/announce https://carbon-bonsai-621.appspot.com/announce https://chihaya-heroku.120181311.xyz/announce https://open.acgnxtracker.com/announce https://opentracker.acgnx.se/announce https://opentracker.cc/announce https://opentracker.i2p.rocks/announce https://opentracker.i2p.rocks:443/announce https://p2p.azu.red:443/announce https://sparkle.ghostchu-services.top/tracker/announce https://sparkle.ghostchu-services.top:443/announce https://t.btcland.xyz/announce https://t.zerg.pw:443/announce https://t-115.rhcloud.com/only_for_ylbud https://tr.abiir.top/announce https://tr.abir.ga/announce https://tr.abir.ga:443/announce https://tr.abirxo.cf/announce https://tr.bangumi.moe:9696/announce https://tr.burnabyhighstar.com/announce https://tr.burnabyhighstar.com:443/announce https://tr.doogh.club/announce https://tr.fuckbitcoin.xyz/announce https://tr.highstar.shop/announce https://tr.nyacat.pw/announce https://tr.nyacat.pw:443/announce https://tr.ready4.icu/announce https://tr.ready4.icu:443/announce https://tr.steins-gate.moe:2096/announce https://tr.torland.ga/announce https://tracker.babico.name.tr/announce https://tracker.bt4g.com:443/announce https://tracker.cloudit.top:443/announce https://tracker.coalition.space/announce https://tracker.cyber-hub.net/announce https://tracker.cyber-hub.net:443/announce https://tracker.feb217.tk:8443/announce https://tracker.foreverpirates.co/announce https://tracker.foreverpirates.co:443/announce https://tracker.gbitt.info/announce https://tracker.gcrenwp.top/announce https://tracker.gcrenwp.top:443/announce https://tracker.imgoingto.icu/announce https://tracker.imgoingto.icu:443/announce https://tracker.ipfsscan.io:443/announce https://tracker.iriseden.fr/announce https://tracker.itscraftsoftware.my.id:443/announce https://tracker.kmzs123.cn/announce https://tracker.kuroy.me/announce https://tracker.kuroy.me:443/announce https://tracker.leechshield.link:443/announce https://tracker.lelux.fi/announce https://tracker.lilithraws.cf/announce https://tracker.lilithraws.org/announce https://tracker.lilithraws.org:443/announce https://tracker.mlsub.net:443/announce https://tracker.nanoha.org/announce https://tracker.nanoha.org:443/announce https://tracker.nitrix.me/announce https://tracker.parrotsec.org/announce https://tracker.pmman.tech:443/announce https://tracker.sloppyta.co/announce https://tracker.tamersunion.org/announce https://tracker.tamersunion.org:443/announce https://tracker.vectahosting.eu:8443/announce https://tracker.yarr.pt/announce https://tracker.yemekyedim.com/announce https://tracker.yemekyedim.com:443/announce https://tracker1.520.jp/announce https://tracker1.520.jp:443/announce https://tracker6.lelux.fi/announce https://trackers.mlsub.net/announce https://trackers.mlsub.net:443/announce https://tracker-zhuqiy.dgj055.icu:443/announce https://trackme.theom.nz/announce https://www.peckservers.com:9443/announce http://[2001:1b10:1000:8101:0:242:ac11:2]:6969/announce http://[2001:470:1:189:0:1:2:3]:6969/announce http://[2a04:ac00:1:3dd8::1:2710]:2710/announce http://0123456789nonexistent.com:80/announce http://0d.kebhana.mx:443/announce http://104.143.10.186:8000/announce http://104.238.198.186:8000/announce http://1337.abcvg.info/announce http://1337.abcvg.info:80/announce http://207.241.226.111:6969/announce http://207.241.231.226:6969/announce http://5rt.tace.ru:60889/announce http://94.228.192.98/announce http://bigfoot1942.sektori.org:6969/announce http://bittorrent-tracker.e-n-c-r-y-p-t.net:1337/announce http://bt.3kb.xyz/announce http://bt.endpot.com:80/announce http://bt.okmp3.ru:2710/announce http://bt.poletracker.org:2710/announce http://bt.sc-ol.com:2710/announce http://bt1.xxxxbt.cc:6969/announce http://btfile.sdo.com:6961/announce http://btracker.top:11451/announce http://buny.uk:6969/announce http://bvarf.tracker.sh:2086/announce http://canardscitrons.nohost.me:6969/announce http://ch3oh.ru:6969/announce http://cloud.nyap2p.com:8080/announce http://dn42.smrsh.net:6969/announce http://exodus.desync.com:6969/announce http://finbytes.org:80/announce.php http://fleira.no:6969/announce http://fosstorrents.com:6969/announce http://ftp.pet:6969/announce http://fxtt.ru/announce http://highteahop.top:6960/announce http://home.yxgz.club:6969/announce http://home.yxgz.vip:6969/announce http://ipv4announce.sktorrent.eu:6969/announce http://ipv6.1337.cx:6969/announce http://ipv6.govt.hu:6969/announce http://ipv6.rer.lol:6969/announce http://mediaclub.tv/announce.php http://milanesitracker.tekcities.com/announce http://montreal.nyap2p.com:8080/announce http://nyaa.tracker.wf:7777/announce http://open.acgnxtracker.com/announce http://open.acgtracker.com:1096/announce http://open.miotracker.com/announce http://open.nyaatorrents.info:6544/announce http://open.tracker.cl:1337/announce http://open.tracker.ink:6969/announce http://open.trackerlist.xyz:80/announce http://openbittorrent.com:80/announce http://opentracker.acgnx.com:6869/announce http://opentracker.acgnx.se/announce http://opentracker.i2p.rocks:6969/announce http://opentracker.xyz/announce http://p2p.0g.cx:6969/announce http://p4p.arenabg.com:1337/announce http://parag.rs:6969/announce http://pow7.com/announce http://public.tracker.vraphim.com:6969/announce http://pubt.net:2710/announce http://retracker.hotplug.ru:2710/announce http://retracker.spark-rostov.ru:80/announce http://rfc5746.mywaifu.best:6969/announce http://rt.optizone.ru/announce http://rt.tace.ru/announce http://saltwood.top:6969/announce http://seeders-paradise.org:80/announce http://servandroidkino.ru:80/announce http://share.camoe.cn:8080/announce http://share.dmhy.org/annonuce http://share.hkg-fansub.info:80/announce.php http://shubt.net:2710/announce http://siambit.org/announce.php http://smurfsoft.com:6969/announce http://sukebei.tracker.wf:8888/announce http://t.acg.rip:6699/announce http://t.jaekr.sh:6969/announce http://t.nyaatracker.com/announce http://t.nyaatracker.com:80/announce http://t.overflow.biz:6969/announce http://t.publictracker.xyz:6969/announce http://t2.popgo.org:7456/annonce http://taciturn-shadow.spb.ru:6969/announce http://tk.greedland.net/announce http://tk.greedland.net:80/announce http://tk.nvacg.org:3333/announce http://torrentsmd.com:8080/announce http://torrenttracker.nwc.acsalaska.net:6969/announce http://tr.bangumi.moe:6969/announce http://tr.cili001.com:8070/announce http://tr.kxmp.cf/announce http://tr.kxmp.cf:80/announce http://tr.nyacat.pw/announce http://tr.nyacat.pw:80/announce http://tr1.aag.moe:2095/announce http://tracker.acgnx.se/announce http://tracker.birkenwald.de:6969/announce http://tracker.bittor.pw:1337/announce http://tracker.bt4g.com:2095/announce http://tracker.btcake.com/announce http://tracker.bt-hash.com:80/announce http://tracker.bz:80/announce http://tracker.corpscorp.online:80/announce http://tracker.dler.com:6969/announce http://tracker.dler.org:6969/announce http://tracker.dmcomic.org:2710/announce http://tracker.dutchtracking.nl/announce http://tracker.edkj.club:6969/announce http://tracker.electro-torrent.pl/announce http://tracker.electro-torrent.pl:80/announce http://tracker.files.fm:6969/announce http://tracker.gbitt.info/announce http://tracker.ipv6tracker.org/announce http://tracker.ipv6tracker.org:80/announce http://tracker.ipv6tracker.ru/announce http://tracker.k.vu:6969/announce http://tracker.kamigami.org:2710/announce http://tracker.ktxp.com:6868/announce http://tracker.ktxp.com:7070/announce http://tracker.lelux.fi/announce http://tracker.lintk.me:2710/announce http://tracker.loadbt.com:6969/announce http://tracker.merded.xyz:8000/announce http://tracker.moxing.party:6969/announce http://tracker.mywaifu.best:6969/announce http://tracker.noobsubs.net/announce http://tracker.nucozer-tracker.ml:2710/announce http://tracker.openbittorrent.com/announce http://tracker.openbittorrent.com:80/announce http://tracker.opentrackr.org:1337/announce http://tracker.peckservers.com:9000/announce http://tracker.qu.ax:6969/announce http://tracker.renfei.net:8080/announce http://tracker.sbsub.com:2710/announce http://tracker.skyts.net:6969/announce http://tracker.srv00.com:6969/announce http://tracker.swateam.org.uk:2710/announce http://tracker.tfile.co/announce http://tracker.tfile.me/announce http://tracker.torrentino.com/announce http://tracker.trackerfix.com/announce http://tracker.vanitycore.co:6969/announce http://tracker.vraphim.com:6969/announce http://tracker.waaa.moe:6969/announce http://tracker.xiaoduola.xyz:6969/announce http://tracker.zerobytes.xyz:1337/announce http://tracker1.bt.moack.co.kr/announce http://tracker1.bt.moack.co.kr:80/announce http://tracker1.itzmx.com:8080/announce http://tracker1.torrentino.com/announce http://tracker2.dler.org/announce http://tracker2.dler.org:80/announce http://tracker2.itzmx.com:6961/announce http://tracker2.torrentino.com/announce http://tracker3.itzmx.com:6961/announce http://tracker3.torrentino.com/announce http://tracker4.itzmx.com:2710/announce http://tracker4.torrentino.com/announce http://tracker5.torrentino.com/announce http://tracker810.xyz:11450/announce http://tracker-cdn.moeking.me:2095/announce http://tracker-zhuqiy.dgj055.icu:80/announce http://uraniumhexafluori.de:1919/announce http://v6-tracker.0g.cx:6969/announce http://vps02.net.orel.ru/announce http://wepzone.net:6969/announce http://www.all4nothin.net/announce.php http://www.all4nothin.net:80/announce.php http://www.genesis-sp.org:2710/announce http://www.torrentsnipe.info:2701/announce http://www.wareztorrent.com/announce http://www.wareztorrent.com:80/announce udp://[2001:1b10:1000:8101:0:242:ac11:2]:6969/announce udp://[2001:470:1:189:0:1:2:3]:6969/announce udp://[2a03:7220:8083:cd00::1]:451/announce udp://[2a04:ac00:1:3dd8::1:2710]:2710/announce udp://[2a0f:e586:f:f::220]:6969/announce udp://[2a0f:e586:f:f::81]:6969/announce udp://104.143.10.186:8000/announce udp://104.238.198.186:8000/announce udp://1c.premierzal.ru:6969/announce udp://207.241.226.111:6969/announce udp://207.241.231.226:6969/announce udp://212.1.226.176:2710/announce udp://47.ip-51-68-199.eu:6969/announce udp://52.58.128.163:6969/announce udp://6ahddutb1ucc3cp.ru:6969/announce udp://6rt.tace.ru:80/announce udp://78.30.254.12:2710/announce udp://7p41a8967.wicp.vip:22222/announce udp://9.rarbg.com:2810/announce udp://9.rarbg.com:2880/announce udp://9.rarbg.com:2930/announce udp://9.rarbg.com:2940/announce udp://9.rarbg.me:2710/announce udp://91.216.110.52:451/announce udp://aaa.army:8866/announce udp://aarsen.me:6969/announce udp://abufinzio.monocul.us:6969/announce udp://acxx.de:6969/announce udp://admin.52ywp.com:6969/announce udp://admin.videoenpoche.info:6969/announce udp://aegir.sexy:6969/announce udp://amigacity.xyz:6969/announce udp://api.clasificadosrapidos.com:53/announce udp://app.icon256.com:8000/announce udp://astrr.ru:6969/announce udp://bandito.byterunner.io:6969/announce udp://bclearning.top:6969/announce udp://bedro.cloud:6969/announce udp://bittorrent-tracker.e-n-c-r-y-p-t.net:1337/announce udp://black-bird.ynh.fr:6969/announce udp://blokas.io:6969/announce udp://bt.ktrackers.com:6666/announce udp://bt.oiyo.tk:6969/announce udp://bt.sc-ol.com:2710/announce udp://bt1.archive.org:6969/announce udp://bt2.archive.org:6969/announce udp://btt.service.gongt.me:43079/announce udp://bubu.mapfactor.com:6969/announce udp://camera.lei001.com:6969/announce udp://canardscitrons.nohost.me:6969/announce udp://carr.codes:6969/announce udp://cdn-1.gamecoast.org:6969/announce udp://code2chicken.nl:6969/announce udp://concen.org:6969/announce udp://coppersurfer.tk:6969/announce udp://cutiegirl.ru:6969/announce udp://d13bttrck.duckdns.org:6969/announce udp://d40969.acod.regrucolo.ru:6969/announce udp://daveking.com:6969/announce udp://discord.heihachi.pw:6969/announce udp://ec2-18-191-163-220.us-east-2.compute.amazonaws.com:6969/announce udp://edu.uifr.ru:6969/announce udp://engplus.ru:6969/announce udp://epider.me:6969/announce udp://evan.im:6969/announce udp://exodus.desync.com:6969/announce udp://explodie.org:6969/announce udp://fe.dealclub.de:6969/announce udp://htz3.noho.st:6969/announce udp://inferno.demonoid.is:3391/announce udp://ipv4.rer.lol:2710/announce udp://ipv4.tracker.harry.lu:80/announce udp://ipv6.69.mu:6969/announce udp://ipv6.babico.name.tr:8000/announce udp://ipv6.tracker.harry.lu:80/announce udp://ipv6.tracker.monitorit4.me:6969/announce udp://isk.richardsw.club:6969/announce udp://ismaarino.com:1234/announce udp://johnrosen1.com:6969/announce udp://jutone.com:6969/announce udp://laze.cc:6969/announce udp://leet-tracker.moe:1337/announce udp://leet-tracker.moe:23861/announce udp://leet-tracker.moe:38151/announce udp://line-net.ru:6969/announce udp://linfan.moe:6969/announce udp://ln.mtahost.co:6969/announce udp://mail.artixlinux.org:6969/announce udp://mail.realliferpg.de:6969/announce udp://martin-gebhardt.eu:25/announce udp://mirror.aptus.co.tz:6969/announce udp://moonburrow.club:6969/announce udp://movies.zsw.ca:6969/announce udp://mts.tvbit.co:6969/announce udp://nagios.tks.sumy.ua:80/announce udp://new-line.net:6969/announce udp://ns1.monolithindustries.com:6969/announce udp://ns-1.x-fins.com:6969/announce udp://odd-hd.fr:6969/announce udp://open.4ever.tk:6969/announce udp://open.demonii.com:1337/announce udp://open.dstud.io:6969/announce udp://open.free-tracker.ga:6969/announce udp://open.publictracker.xyz:6969/announce udp://open.stealth.si:80/announce udp://open.tracker.cl:1337/announce udp://open.tracker.ink:6969/announce udp://open.u-p.pw:6969/announce udp://opentor.org:2710/announce udp://opentracker.i2p.rocks:6969/announce udp://opentracker.io:6969/announce udp://p2p.publictracker.xyz:6969/announce udp://p4p.arenabg.ch:1337/announce udp://p4p.arenabg.com:1337/announce udp://peru.subventas.com:53/announce udp://pow7.com:80/announce udp://private.anonseed.com:6969/announce udp://psyco.fr:6969/announce udp://public.publictracker.xyz:6969/announce udp://public.tracker.vraphim.com:6969/announce udp://rep-art.ynh.fr:6969/announce udp://retracker.hotplug.ru:2710/announce udp://retracker.lanta.me:2710/announce udp://retracker.lanta-net.ru:2710/announce udp://retracker.netbynet.ru:2710/announce udp://retracker01-msk-virt.corbina.net:80/announce udp://run.publictracker.xyz:6969/announce udp://ryjer.com:6969/announce udp://sanincode.com:6969/announce udp://sd-161673.dedibox.fr:6969/announce udp://seedpeer.net:6969/announce udp://serpb.vpsburti.com:6969/announce udp://static.46.73.46.78.clients.your-server.de:8000/announce udp://static.54.161.216.95.clients.your-server.de:6969/announce udp://storage.groupees.com:6969/announce udp://t.nyaatracker.com:80/announce udp://t.overflow.biz:6969/announce udp://t.zerg.pw:6969/announce udp://t2.leech.ie:1337/announce udp://tamas3.ynh.fr:6969/announce udp://thagoat.rocks:6969/announce udp://thetracker.org:80/announce udp://thinking.duckdns.org:6969/announce udp://thouvenin.cloud:6969/announce udp://tk2v6.trackerservers.com:8080/announce udp://torrentclub.online:54123/announce udp://torrentclub.space:6969/announce udp://torrents.artixlinux.org:6969/announce udp://tr.bangumi.moe:6969/announce udp://tr.cili001.com:8070/announce udp://tr2.cubonegro.lol:6969/announce udp://tr4ck3r.duckdns.org:6969/announce udp://trackarr.org:6969/announce udp://tracker.0x.tf:6969/announce udp://tracker.0x7c0.com:6969/announce udp://tracker.4.babico.name.tr:3131/announce udp://tracker.altrosky.nl:6969/announce udp://tracker.auctor.tv:6969/announce udp://tracker.babico.name.tr:8000/announce udp://tracker.beeimg.com:6969/announce udp://tracker.birkenwald.de:6969/announce udp://tracker.bitsearch.to:1337/announce udp://tracker.bittor.pw:1337/announce udp://tracker.breizh.pm:6969/announce udp://tracker.ccp.ovh:6969/announce udp://tracker.cloaka.xyz:1337/announce udp://tracker.coppersurfer.tk:6969/announce udp://tracker.cubonegro.lol:6969/announce udp://tracker.cyberia.is:6969/announce udp://tracker.darkness.services:6969/announce udp://tracker.ddunlimited.net:6969/announce udp://tracker.deadorbit.nl:6969/announce udp://tracker.dix.tf:6969/announce udp://tracker.dler.com:6969/announce udp://tracker.dler.org:6969/announce udp://tracker.doko.moe:6969/announce udp://tracker.ds.is:6969/announce udp://tracker.dump.cl:6969/announce udp://tracker.edkj.club:6969/announce udp://tracker.e-utp.net:6969/announce udp://tracker.ex.ua:80/announce udp://tracker.farted.net:6969/announce udp://tracker.fatkhoala.org:13710/announce udp://tracker.filemail.com:6969/announce udp://tracker.fnix.net:6969/announce udp://tracker.gigantino.net:6969/announce udp://tracker.gmi.gd:6969/announce udp://tracker.internetwarriors.net:1337/announce udp://tracker.jamesthebard.net:6969/announce udp://tracker.jonaslsa.com:6969/announce udp://tracker.jordan.im:6969/announce udp://tracker.joybomb.tw:6969/announce udp://tracker.kamigami.org:2710/announce udp://tracker.leech.ie:1337/announce udp://tracker.lelux.fi:6969/announce udp://tracker.loadbt.com:6969/announce udp://tracker.merded.xyz:8000/announce udp://tracker.moeking.eu.org:6969/announce udp://tracker.moeking.me:6969/announce udp://tracker.monitorit4.me:6969/announce udp://tracker.ololosh.space:6969/announce udp://tracker.openbittorrent.com:6969/announce udp://tracker.openbittorrent.com:80/announce udp://tracker.opentrackr.org:1337/announce udp://tracker.pomf.se:80/announce udp://tracker.prq.to:80/announce udp://tracker.publicbt.com:80/announce udp://tracker.publictracker.xyz:6969/announce udp://tracker.qu.ax:6969/announce udp://tracker.shkinev.me:6969/announce udp://tracker.sigterm.xyz:6969/announce udp://tracker.skynetcloud.site:6969/announce udp://tracker.skyts.net:6969/announce udp://tracker.srv00.com:6969/announce udp://tracker.swateam.org.uk:2710/announce udp://tracker.sylphix.com:6969/announce udp://tracker.theoks.net:6969/announce udp://tracker.tiny-vps.com:6969/announce udp://tracker.torrent.eu.org:451/announce udp://tracker.torrust-demo.com:6969/announce udp://tracker.tryhackx.org:6969/announce udp://tracker.uw0.xyz:6969/announce udp://tracker.v6speed.org:6969/announce udp://tracker.waaa.moe:6969/announce udp://tracker.zemoj.com:6969/announce udp://tracker.zerobytes.xyz:1337/announce udp://tracker0.ufibox.com:6969/announce udp://tracker1.bt.moack.co.kr:80/announce udp://tracker1.itzmx.com:8080/announce udp://tracker1.myporn.club:9337/announce udp://tracker2.dler.com:80/announce udp://tracker2.dler.org:80/announce udp://tracker2.itzmx.com:6961/announce udp://tracker3.itzmx.com:6961/announce udp://tracker4.itzmx.com:2710/announce udp://tracker4.leechshield.link:6969/announce udp://tracker6.lelux.fi:6969/announce udp://trackerb.jonaslsa.com:6969/announce udp://tracker-udp.gbitt.info:80/announce udp://tsundere.pw:6969/announce udp://ttk2.nbaonlineservice.com:6969/announce udp://u4.trakx.crim.ist:1337/announce udp://u6.trakx.crim.ist:1337/announce udp://udp.yarr.pt:1337/announce udp://udp-tracker.shittyurl.org:6969/announce udp://us-tracker.publictracker.xyz:6969/announce udp://v1046920.hosted-by-vdsina.ru:6969/announce udp://v2.iperson.xyz:6969/announce udp://valakas.rollo.dnsabr.com:2710/announce udp://vibe.community:6969/announce udp://vibe.sleepyinternetfun.xyz:1738/announce udp://wepzone.net:6969/announce udp://www.torrent.eu.org:451/announce udp://z.mercax.com:53/announce udp://zecircle.xyz:6969/announce ws://hub.bugout.link:80/announce wss://tracker.openwebtorrent.com:443/announce ================================================ FILE: use_openssl_md5_sha1.patch ================================================ From: CarterLi Date: Fri, 15 Jun 2018 14:58:09 +0800 Subject: [PATCH] Use openssl md5 / sha1 Link: https://github.com/kn007/patch/issues/5 diff -uNr a/auto/sources b/auto/sources --- a/auto/sources 2020-03-03 23:04:21.000000000 +0800 +++ b/auto/sources 2020-03-13 22:00:37.317527023 +0800 @@ -60,8 +60,6 @@ src/core/ngx_file.c \ src/core/ngx_crc32.c \ src/core/ngx_murmurhash.c \ - src/core/ngx_md5.c \ - src/core/ngx_sha1.c \ src/core/ngx_rbtree.c \ src/core/ngx_radix_tree.c \ src/core/ngx_slab.c \ diff -uNr a/src/core/ngx_md5.c b/src/core/ngx_md5.c --- a/src/core/ngx_md5.c 2020-03-03 23:04:21.000000000 +0800 +++ /dev/null 1970-01-01 08:00:00.000000000 +0800 @@ -1,283 +0,0 @@ - -/* - * An internal implementation, based on Alexander Peslyak's - * public domain implementation: - * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 - */ - - -#include -#include -#include - - -static const u_char *ngx_md5_body(ngx_md5_t *ctx, const u_char *data, - size_t size); - - -void -ngx_md5_init(ngx_md5_t *ctx) -{ - ctx->a = 0x67452301; - ctx->b = 0xefcdab89; - ctx->c = 0x98badcfe; - ctx->d = 0x10325476; - - ctx->bytes = 0; -} - - -void -ngx_md5_update(ngx_md5_t *ctx, const void *data, size_t size) -{ - size_t used, free; - - used = (size_t) (ctx->bytes & 0x3f); - ctx->bytes += size; - - if (used) { - free = 64 - used; - - if (size < free) { - ngx_memcpy(&ctx->buffer[used], data, size); - return; - } - - ngx_memcpy(&ctx->buffer[used], data, free); - data = (u_char *) data + free; - size -= free; - (void) ngx_md5_body(ctx, ctx->buffer, 64); - } - - if (size >= 64) { - data = ngx_md5_body(ctx, data, size & ~(size_t) 0x3f); - size &= 0x3f; - } - - ngx_memcpy(ctx->buffer, data, size); -} - - -void -ngx_md5_final(u_char result[16], ngx_md5_t *ctx) -{ - size_t used, free; - - used = (size_t) (ctx->bytes & 0x3f); - - ctx->buffer[used++] = 0x80; - - free = 64 - used; - - if (free < 8) { - ngx_memzero(&ctx->buffer[used], free); - (void) ngx_md5_body(ctx, ctx->buffer, 64); - used = 0; - free = 64; - } - - ngx_memzero(&ctx->buffer[used], free - 8); - - ctx->bytes <<= 3; - ctx->buffer[56] = (u_char) ctx->bytes; - ctx->buffer[57] = (u_char) (ctx->bytes >> 8); - ctx->buffer[58] = (u_char) (ctx->bytes >> 16); - ctx->buffer[59] = (u_char) (ctx->bytes >> 24); - ctx->buffer[60] = (u_char) (ctx->bytes >> 32); - ctx->buffer[61] = (u_char) (ctx->bytes >> 40); - ctx->buffer[62] = (u_char) (ctx->bytes >> 48); - ctx->buffer[63] = (u_char) (ctx->bytes >> 56); - - (void) ngx_md5_body(ctx, ctx->buffer, 64); - - result[0] = (u_char) ctx->a; - result[1] = (u_char) (ctx->a >> 8); - result[2] = (u_char) (ctx->a >> 16); - result[3] = (u_char) (ctx->a >> 24); - result[4] = (u_char) ctx->b; - result[5] = (u_char) (ctx->b >> 8); - result[6] = (u_char) (ctx->b >> 16); - result[7] = (u_char) (ctx->b >> 24); - result[8] = (u_char) ctx->c; - result[9] = (u_char) (ctx->c >> 8); - result[10] = (u_char) (ctx->c >> 16); - result[11] = (u_char) (ctx->c >> 24); - result[12] = (u_char) ctx->d; - result[13] = (u_char) (ctx->d >> 8); - result[14] = (u_char) (ctx->d >> 16); - result[15] = (u_char) (ctx->d >> 24); - - ngx_memzero(ctx, sizeof(*ctx)); -} - - -/* - * The basic MD5 functions. - * - * F and G are optimized compared to their RFC 1321 definitions for - * architectures that lack an AND-NOT instruction, just like in - * Colin Plumb's implementation. - */ - -#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) -#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) -#define H(x, y, z) ((x) ^ (y) ^ (z)) -#define I(x, y, z) ((y) ^ ((x) | ~(z))) - -/* - * The MD5 transformation for all four rounds. - */ - -#define STEP(f, a, b, c, d, x, t, s) \ - (a) += f((b), (c), (d)) + (x) + (t); \ - (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ - (a) += (b) - -/* - * SET() reads 4 input bytes in little-endian byte order and stores them - * in a properly aligned word in host byte order. - * - * The check for little-endian architectures that tolerate unaligned - * memory accesses is just an optimization. Nothing will break if it - * does not work. - */ - -#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED) - -#define SET(n) (*(uint32_t *) &p[n * 4]) -#define GET(n) (*(uint32_t *) &p[n * 4]) - -#else - -#define SET(n) \ - (block[n] = \ - (uint32_t) p[n * 4] | \ - ((uint32_t) p[n * 4 + 1] << 8) | \ - ((uint32_t) p[n * 4 + 2] << 16) | \ - ((uint32_t) p[n * 4 + 3] << 24)) - -#define GET(n) block[n] - -#endif - - -/* - * This processes one or more 64-byte data blocks, but does not update - * the bit counters. There are no alignment requirements. - */ - -static const u_char * -ngx_md5_body(ngx_md5_t *ctx, const u_char *data, size_t size) -{ - uint32_t a, b, c, d; - uint32_t saved_a, saved_b, saved_c, saved_d; - const u_char *p; -#if !(NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED) - uint32_t block[16]; -#endif - - p = data; - - a = ctx->a; - b = ctx->b; - c = ctx->c; - d = ctx->d; - - do { - saved_a = a; - saved_b = b; - saved_c = c; - saved_d = d; - - /* Round 1 */ - - STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7); - STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12); - STEP(F, c, d, a, b, SET(2), 0x242070db, 17); - STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22); - STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7); - STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12); - STEP(F, c, d, a, b, SET(6), 0xa8304613, 17); - STEP(F, b, c, d, a, SET(7), 0xfd469501, 22); - STEP(F, a, b, c, d, SET(8), 0x698098d8, 7); - STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12); - STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17); - STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22); - STEP(F, a, b, c, d, SET(12), 0x6b901122, 7); - STEP(F, d, a, b, c, SET(13), 0xfd987193, 12); - STEP(F, c, d, a, b, SET(14), 0xa679438e, 17); - STEP(F, b, c, d, a, SET(15), 0x49b40821, 22); - - /* Round 2 */ - - STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5); - STEP(G, d, a, b, c, GET(6), 0xc040b340, 9); - STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14); - STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20); - STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5); - STEP(G, d, a, b, c, GET(10), 0x02441453, 9); - STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14); - STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20); - STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5); - STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9); - STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14); - STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20); - STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5); - STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9); - STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14); - STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20); - - /* Round 3 */ - - STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4); - STEP(H, d, a, b, c, GET(8), 0x8771f681, 11); - STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16); - STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23); - STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4); - STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11); - STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16); - STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23); - STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4); - STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11); - STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16); - STEP(H, b, c, d, a, GET(6), 0x04881d05, 23); - STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4); - STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11); - STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16); - STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23); - - /* Round 4 */ - - STEP(I, a, b, c, d, GET(0), 0xf4292244, 6); - STEP(I, d, a, b, c, GET(7), 0x432aff97, 10); - STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15); - STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21); - STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6); - STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10); - STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15); - STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21); - STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6); - STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10); - STEP(I, c, d, a, b, GET(6), 0xa3014314, 15); - STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21); - STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6); - STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10); - STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15); - STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21); - - a += saved_a; - b += saved_b; - c += saved_c; - d += saved_d; - - p += 64; - - } while (size -= 64); - - ctx->a = a; - ctx->b = b; - ctx->c = c; - ctx->d = d; - - return p; -} diff -uNr a/src/core/ngx_md5.h b/src/core/ngx_md5.h --- a/src/core/ngx_md5.h 2020-03-03 23:04:21.000000000 +0800 +++ b/src/core/ngx_md5.h 2020-03-13 22:00:37.318527030 +0800 @@ -12,17 +12,13 @@ #include #include +#include -typedef struct { - uint64_t bytes; - uint32_t a, b, c, d; - u_char buffer[64]; -} ngx_md5_t; +typedef MD5_CTX ngx_md5_t; - -void ngx_md5_init(ngx_md5_t *ctx); -void ngx_md5_update(ngx_md5_t *ctx, const void *data, size_t size); -void ngx_md5_final(u_char result[16], ngx_md5_t *ctx); +#define ngx_md5_init MD5_Init +#define ngx_md5_update MD5_Update +#define ngx_md5_final MD5_Final #endif /* _NGX_MD5_H_INCLUDED_ */ diff -uNr a/src/core/ngx_sha1.c b/src/core/ngx_sha1.c --- a/src/core/ngx_sha1.c 2020-03-03 23:04:21.000000000 +0800 +++ /dev/null 1970-01-01 08:00:00.000000000 +0800 @@ -1,294 +0,0 @@ - -/* - * Copyright (C) Maxim Dounin - * Copyright (C) Nginx, Inc. - * - * An internal SHA1 implementation. - */ - - -#include -#include -#include - - -static const u_char *ngx_sha1_body(ngx_sha1_t *ctx, const u_char *data, - size_t size); - - -void -ngx_sha1_init(ngx_sha1_t *ctx) -{ - ctx->a = 0x67452301; - ctx->b = 0xefcdab89; - ctx->c = 0x98badcfe; - ctx->d = 0x10325476; - ctx->e = 0xc3d2e1f0; - - ctx->bytes = 0; -} - - -void -ngx_sha1_update(ngx_sha1_t *ctx, const void *data, size_t size) -{ - size_t used, free; - - used = (size_t) (ctx->bytes & 0x3f); - ctx->bytes += size; - - if (used) { - free = 64 - used; - - if (size < free) { - ngx_memcpy(&ctx->buffer[used], data, size); - return; - } - - ngx_memcpy(&ctx->buffer[used], data, free); - data = (u_char *) data + free; - size -= free; - (void) ngx_sha1_body(ctx, ctx->buffer, 64); - } - - if (size >= 64) { - data = ngx_sha1_body(ctx, data, size & ~(size_t) 0x3f); - size &= 0x3f; - } - - ngx_memcpy(ctx->buffer, data, size); -} - - -void -ngx_sha1_final(u_char result[20], ngx_sha1_t *ctx) -{ - size_t used, free; - - used = (size_t) (ctx->bytes & 0x3f); - - ctx->buffer[used++] = 0x80; - - free = 64 - used; - - if (free < 8) { - ngx_memzero(&ctx->buffer[used], free); - (void) ngx_sha1_body(ctx, ctx->buffer, 64); - used = 0; - free = 64; - } - - ngx_memzero(&ctx->buffer[used], free - 8); - - ctx->bytes <<= 3; - ctx->buffer[56] = (u_char) (ctx->bytes >> 56); - ctx->buffer[57] = (u_char) (ctx->bytes >> 48); - ctx->buffer[58] = (u_char) (ctx->bytes >> 40); - ctx->buffer[59] = (u_char) (ctx->bytes >> 32); - ctx->buffer[60] = (u_char) (ctx->bytes >> 24); - ctx->buffer[61] = (u_char) (ctx->bytes >> 16); - ctx->buffer[62] = (u_char) (ctx->bytes >> 8); - ctx->buffer[63] = (u_char) ctx->bytes; - - (void) ngx_sha1_body(ctx, ctx->buffer, 64); - - result[0] = (u_char) (ctx->a >> 24); - result[1] = (u_char) (ctx->a >> 16); - result[2] = (u_char) (ctx->a >> 8); - result[3] = (u_char) ctx->a; - result[4] = (u_char) (ctx->b >> 24); - result[5] = (u_char) (ctx->b >> 16); - result[6] = (u_char) (ctx->b >> 8); - result[7] = (u_char) ctx->b; - result[8] = (u_char) (ctx->c >> 24); - result[9] = (u_char) (ctx->c >> 16); - result[10] = (u_char) (ctx->c >> 8); - result[11] = (u_char) ctx->c; - result[12] = (u_char) (ctx->d >> 24); - result[13] = (u_char) (ctx->d >> 16); - result[14] = (u_char) (ctx->d >> 8); - result[15] = (u_char) ctx->d; - result[16] = (u_char) (ctx->e >> 24); - result[17] = (u_char) (ctx->e >> 16); - result[18] = (u_char) (ctx->e >> 8); - result[19] = (u_char) ctx->e; - - ngx_memzero(ctx, sizeof(*ctx)); -} - - -/* - * Helper functions. - */ - -#define ROTATE(bits, word) (((word) << (bits)) | ((word) >> (32 - (bits)))) - -#define F1(b, c, d) (((b) & (c)) | ((~(b)) & (d))) -#define F2(b, c, d) ((b) ^ (c) ^ (d)) -#define F3(b, c, d) (((b) & (c)) | ((b) & (d)) | ((c) & (d))) - -#define STEP(f, a, b, c, d, e, w, t) \ - temp = ROTATE(5, (a)) + f((b), (c), (d)) + (e) + (w) + (t); \ - (e) = (d); \ - (d) = (c); \ - (c) = ROTATE(30, (b)); \ - (b) = (a); \ - (a) = temp; - - -/* - * GET() reads 4 input bytes in big-endian byte order and returns - * them as uint32_t. - */ - -#define GET(n) \ - ((uint32_t) p[n * 4 + 3] | \ - ((uint32_t) p[n * 4 + 2] << 8) | \ - ((uint32_t) p[n * 4 + 1] << 16) | \ - ((uint32_t) p[n * 4] << 24)) - - -/* - * This processes one or more 64-byte data blocks, but does not update - * the bit counters. There are no alignment requirements. - */ - -static const u_char * -ngx_sha1_body(ngx_sha1_t *ctx, const u_char *data, size_t size) -{ - uint32_t a, b, c, d, e, temp; - uint32_t saved_a, saved_b, saved_c, saved_d, saved_e; - uint32_t words[80]; - ngx_uint_t i; - const u_char *p; - - p = data; - - a = ctx->a; - b = ctx->b; - c = ctx->c; - d = ctx->d; - e = ctx->e; - - do { - saved_a = a; - saved_b = b; - saved_c = c; - saved_d = d; - saved_e = e; - - /* Load data block into the words array */ - - for (i = 0; i < 16; i++) { - words[i] = GET(i); - } - - for (i = 16; i < 80; i++) { - words[i] = ROTATE(1, words[i - 3] ^ words[i - 8] ^ words[i - 14] - ^ words[i - 16]); - } - - /* Transformations */ - - STEP(F1, a, b, c, d, e, words[0], 0x5a827999); - STEP(F1, a, b, c, d, e, words[1], 0x5a827999); - STEP(F1, a, b, c, d, e, words[2], 0x5a827999); - STEP(F1, a, b, c, d, e, words[3], 0x5a827999); - STEP(F1, a, b, c, d, e, words[4], 0x5a827999); - STEP(F1, a, b, c, d, e, words[5], 0x5a827999); - STEP(F1, a, b, c, d, e, words[6], 0x5a827999); - STEP(F1, a, b, c, d, e, words[7], 0x5a827999); - STEP(F1, a, b, c, d, e, words[8], 0x5a827999); - STEP(F1, a, b, c, d, e, words[9], 0x5a827999); - STEP(F1, a, b, c, d, e, words[10], 0x5a827999); - STEP(F1, a, b, c, d, e, words[11], 0x5a827999); - STEP(F1, a, b, c, d, e, words[12], 0x5a827999); - STEP(F1, a, b, c, d, e, words[13], 0x5a827999); - STEP(F1, a, b, c, d, e, words[14], 0x5a827999); - STEP(F1, a, b, c, d, e, words[15], 0x5a827999); - STEP(F1, a, b, c, d, e, words[16], 0x5a827999); - STEP(F1, a, b, c, d, e, words[17], 0x5a827999); - STEP(F1, a, b, c, d, e, words[18], 0x5a827999); - STEP(F1, a, b, c, d, e, words[19], 0x5a827999); - - STEP(F2, a, b, c, d, e, words[20], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[21], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[22], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[23], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[24], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[25], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[26], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[27], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[28], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[29], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[30], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[31], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[32], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[33], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[34], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[35], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[36], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[37], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[38], 0x6ed9eba1); - STEP(F2, a, b, c, d, e, words[39], 0x6ed9eba1); - - STEP(F3, a, b, c, d, e, words[40], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[41], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[42], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[43], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[44], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[45], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[46], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[47], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[48], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[49], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[50], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[51], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[52], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[53], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[54], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[55], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[56], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[57], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[58], 0x8f1bbcdc); - STEP(F3, a, b, c, d, e, words[59], 0x8f1bbcdc); - - STEP(F2, a, b, c, d, e, words[60], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[61], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[62], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[63], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[64], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[65], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[66], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[67], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[68], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[69], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[70], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[71], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[72], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[73], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[74], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[75], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[76], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[77], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[78], 0xca62c1d6); - STEP(F2, a, b, c, d, e, words[79], 0xca62c1d6); - - a += saved_a; - b += saved_b; - c += saved_c; - d += saved_d; - e += saved_e; - - p += 64; - - } while (size -= 64); - - ctx->a = a; - ctx->b = b; - ctx->c = c; - ctx->d = d; - ctx->e = e; - - return p; -} diff -uNr a/src/core/ngx_sha1.h b/src/core/ngx_sha1.h --- a/src/core/ngx_sha1.h 2020-03-03 23:04:21.000000000 +0800 +++ b/src/core/ngx_sha1.h 2020-03-13 22:00:37.319527037 +0800 @@ -12,17 +12,13 @@ #include #include +#include -typedef struct { - uint64_t bytes; - uint32_t a, b, c, d, e, f; - u_char buffer[64]; -} ngx_sha1_t; +typedef SHA_CTX ngx_sha1_t; - -void ngx_sha1_init(ngx_sha1_t *ctx); -void ngx_sha1_update(ngx_sha1_t *ctx, const void *data, size_t size); -void ngx_sha1_final(u_char result[20], ngx_sha1_t *ctx); +#define ngx_sha1_init SHA1_Init +#define ngx_sha1_update SHA1_Update +#define ngx_sha1_final SHA1_Final #endif /* _NGX_SHA1_H_INCLUDED_ */