NLNOG Day: konfigurace směrovačů, síťování tábora i hackování aut

18. 9. 2018
Doba čtení: 8 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
Sdružení provozovatelů internetových sítí v Nizozemsku pořádá pravidelné technologické konference. V loňském roce se probíraly mimo jiné nové vlastnosti BGP, síťování letního tábora, správa konfigurace i hackování aut.

V Nizozemsku sídlí významné množství společností, které mají globální přesah. Jsou jimi například RIPE NCC, regionální internetový registr, NLnet Labs, společnost vyvíjející open source software, nebo třeba jeden z nejstarších peeringových uzlů AMS-IX. Není proto divu, že o technická témata není na konferenci NLNOG Day nouze. Tento článek popisuje loňský ročník konference, který proběhl 8. září 2017.

Graceful BGP session shutdown

Job Snijders představil mechanizmus Graceful BGP session shutdown – jednoduché rozšíření BGP pro zpříjemnění odstávek BGP směrovačů: „Problém dnes je, že při plánované odstávce riskujete, že nějakou chvíli nebude něco fungovat, než se spočítá nová cesta.“ Při běžném vypnutí BGP relace se v síti partnera nejdříve šíří informace o zrušení prefixu a teprve potom jde informace o záložní cestě. Po krátkou dobu tedy daný prefix není dostupný.

Job Snijders

Při použití graceful shutdown směrovač nejprve signalizuje prefixy se speciální komunitou GRACEFUL_SHUTDOWN. Ta způsobí, že si partner u daného prefixu nastaví nejnižší lokální preferenci a začne hledat novou, lepší cestu. Provoz se tedy přesměruje na záložní linku ještě předtím, než vypínaná BGP relace přestane fungovat. K podpoře na straně příjemce stačí, aby v případě, kdy dostane prefix se speciální komunitou 65535:0, snížil lokální preferenci spoje na minimum.

Odesílatel by měl chvíli před vypnutím relace nastavit tuto komunitu na všechny adresy odstavovaného směrovače. Měla by zůstat i během odstávky a až po kontrole, že všechno funguje, je možné ji odstranit. V Cisco IOS XE jsou oba kroky spojené automaticky do příkazu graceful-maintanance activate. V síti operátorů NTT, GTT, GitHub, Nordunet, ColoClue a dalších je přijímací funkce implementována. Vzhledem k tomu, že daná komunita je tranzitivní, netýká se jen přímo připojených partnerů, ale projde i komplexnější strukturou sítí. Problém je, že to trochu nabourává zažitý model, kdy tranzitní operátoři na komunity nehledí. Případné zneužití je ale možné odhalit snadno, stačí monitorovat, že někdo vysílá tuto speciální komunitu déle než několik desítek minut.

Jak zasíťovat letní tábor

SHA2017 je pětidenní letní tábor pro hackery. Jako takový se kromě stanů neobejde ani bez elektřiny a připojení k internetu. O internet do každého stanu, chatky i loďky na ploše jednoho čtverečního kilometru se stará mezinárodní skupina dobrovolníků Team:NOC, která podobným způsobem zajišťuje i obdobné akce německého Chaos Computer Club. „Jedná se vlastně o malého internetového poskytovatele s dočasnými IP adresami a vlastním čísle autonomního systému,“ vysvětluje Arjan Koopen. Provozují infrastrukturu pro drátové a bezdrátové připojení účastníků, video streaming, VoIP, nebo třeba skenování vstupenek. Bylo položeno několik jednovidových vláken, celkem asi 50 kabelů. Jako PoP sítě se používá kadibudka – řešení zvané datenklo. Wi-Fi AP je na dřevěném sloupku v komínku budky. Uvnitř je přepínač Arista.

Datibudka – kromě distribuce internetu slouží i mnohem prozaičtějšímu účelu.

Na místě byla také tři datacentra. Jedno byla už přítomno dříve, bylo však potřeba nainstalovat klimatizaci, dvě vznikla dočasně kvůli táboru. Vlastní připojení k internetu zajišťovalo 50km temné vlákno od nizozemského operátora UNET, osazené 100GbE připojením do datacentra. Vzhledem k tomu, celá trasa měla útlum jen 13.5 dB na 1550 nm, instalovali na stejném vlákně ještě záložní trasu s 10GbE a pasivními DWDM multiplexory. Nakonec se využilo špičkově jen 13 Gbps, což aspoň částečně ospravedlnilo použití 100GbE technologie.

A jak vypadá vaše datacentrum?

K síti bylo připojeno celkem 8300 zařízení, z toho například 2000 zařízení s čipsetem ESP8266, která sloužila jako konferenční visačky. Další zajímavé zařízení byl Pixelflut – velká LED stěna, kterou je možné ovládat přes internet. „Taková věc potřebuje hodně přenosu, teklo do ní asi 4,5 Gbps,“ upřesňuje Arjan Koopen. Vzhledem k tomu, že organizátoři měli k dispozici především datacentrové přepínače, museli přehodnotit předchozí přístup s jedním centrálním směrovačem a velkým množstvím L2 přepínačů. Místo prováděli směrování na více bodech – v každé datibudce. Poprvé také mohli vyzkoušet tzv. white-label směrovače, tedy síťový hardware, ve kterém běží open source software – konkrétně Cumulus Linux. Problémem se třeba ukázala implementace protokolu OSPFv3 v démonu Quagga, která způsobovala pád směrovače. Řešením byla výměna démona Quagga za BIRD. I tak se ale objevovala velká zátěž control plane na směrovači, který měl směrovat 8192 veřejných IPv4 adres pro místní Wi-Fi. Část dokumentace síťové infrastruktury je zveřejněna na GitHubu.

Kolmo: řídit Linux jako síťové zařízení

Psal se rok 2002 a Bert Hubert, dnešní vývojář PowerDNS, byl přizván ke konzultaci do mezinárodní agentury UNHCR. Jejich satelitní spojení, které sloužilo VoIP i data, nefungovalo dobře a dodavatelé velkých značek si s tím nedokázali poradit. Bert tehdy nahradil komerční směrovače linuxovými a nastavil nejrůznější parametry QoS tak, aby nedocházelo k Bufferbloatu a aby VoIP hovory fungovaly i v případě, kdy se stahuje elektronická pošta. V té době také vznikl známý skript Wondershaper.

Bert Hubert

Velkým nedostatkem linuxových síťových zařízení proti zařízením výrobců jako Cisco, Juniper nebo Mikrotik je konfigurační roztříštěnost. „Když do Linuxu tři dny bušíte, abyste nastavili síťový stack do optimálního stavu, nemáte jak z něj tu konfiguraci dostat, aby přežila například restart.“ Jedinou výjimkou je nástroj iptables, který má příkazy iptables-save a iptables-restore. Zbytek nastavení sítě nejde nijak uložit. V historii se objevilo několik způsobů, jak tomuto problému čelit.

Přístup z roku 2000 počítá s tím, že všechno se nakonfiguruje v /etc/network/interfaces  a za běhu už se na to nastavení nijak nesahá. Řešení z roku 2014 používá například Ansible playbook, který nastaví soubor interfaces a případně i další. Jde jen o přidání další vrstvy abstrakce bez jakéhokoli posunu v původním problému. Ideálním stavem by bylo mít možnost server libovolně konfigurovat a následně z něj dostat playbook, který takový stav nastaví. Přitom je ale problém, když playbook pracuje jen s konkrétní verzí operačního systému, protože přímo nahrazuje určité konfigurační soubory.

Kolmo je myšlenka, jak získat programový přístup ke konfiguraci. Název odkazuje na pojem kolmogorovská složitost z teoretické informatiky, který hodnotí složitost souboru dat podle délky programu, který takový soubor vygeneruje.

Kolmo používá Configuration Schema File: soubor popisující formát konfiguračního souboru – jaké jsou volby, jaké hodnoty se čekají, jaké jsou jednotky. Dále Kolmo spravuje Stored Configuration File, kde jsou uchovány pouze změny proti výchozí konfiguraci. Celé to zastřešuje knihovna  libkolmo.

Výhodou je, že taková konfigurace je sebe dokumentující a navíc minimální, takže je velmi dobře přenositelná. Knihovna kolmo také automaticky ukládá historii konfigurací, takže je možné se k nim vrátit. Při upgradu je možné získat plnou konfiguraci, včetně výchozích voleb, takže i v případě, že se mezi verzemi změnila, nová minimální konfigurace bude jiná, ale chování serveru bude stejné.

Jak se hackují auta

Už v roce 2015 bylo 30 procent aut připojeno k internetu. Jejich množství bude nepochybně přibývat. Hackování aut začalo s BMW v roce 1988, což bylo první auto se sběrnicí CAN. Ta se postupně rozšiřuje do stále většího množství komponent. Sběrnice samozřejmě nemá žádné prostředky k autentizaci, každý se na ni může vydávat za kohokoli. Problém je, že obsah zpráv je pro běžného pozorovatele nečitelný a ani není snadné zjistit, které adresy patří kterému zařízení. Také v autě bývá několik oddělených CAN sběrnic, takže ta připojená k motoru není zároveň připojena k zábavnímu systému. Nicméně i tak je potřeba brána, která dodává informace z motoru na palubní desku a případně povoluje přenos určitých zpráv opačným směrem.

Daan Keuper

Sběrnice CAN je zapojena tak, že při vysílání je dominantní nula, která v případě kolize vyhraje. Zařízení s nižší adresou má tedy větší prioritu. Při detekci kolize se zařízení pokouší vysílání opakovat 128krát, pak pokus vzdá.

Existuje také například útok na odemykání auta bez klíče, jednoduše prostřednictvím retranslace rádiového signálu z auta k řidiči a zpět. Dalším útokem je spoofing zpráv TPMS, tedy systému monitoringu tlaku pneumatik. Auto se při ztrátě tlaku zastaví, nebo přejde do „želvího módu“. Tento útok údajně využívají některé policejní složky při stíhání podezřelého auta.

Největší dírou ale nepochybně je IVI – In Vehicle Infotainment. V roce 2012 někdo předvedl přístup ke CAN sběrnici přehráním zlomyslného MP3 souboru. Zcela vzdálený útok byl předveden na autě značky Jeep. Dbus démon v IVI poslouchal na rozhraní vestavěného celulárního modemu. Byla v něm zranitelnost, která umožnila získat shell superuživatele. IVI v tomto autě byl připojen ke všem CAN sběrnicím, takže útoku nic nebránilo.

Také byl předveden útok na auta Tesla. Ten vyžadoval otevření zlomyslné webové stránky. I přesto, že Tesla má bránu mezi CAN sběrnicemi, která přístup k motoru blokuje, IVI může aktualizovat firmware v této bráně, přičemž tyto firmwary nebyly podepsány.

Nejde o ojedinělé případy. Lidé ze společnosti Computest mají k dispozici plně vzdálený útok, který umožňuje u určitého modelu auta získat shell superuživatele v IVI. Záleží samozřejmě na mobilním operátorovi, aby nepoužíval CGN a neblokoval příchozí provoz na mobilní terminál v autě. V autě je mobilní terminál připojený k procesoru A, ten je ethernetem propojen k procesoru B, odtud vede sériová sběrnice k řadiči sběrnice CAN, která je připojena k bráně. Podařilo se jim nahrát z procesoru A nový firmware do procesoru B a dostat se tedy až na první CAN sběrnici. Skutečnému napadení auta brání CAN brána. Je však možné získávat data z mikrofonu, GPS a podobně.

Zajímavý útočný vektor na auta představuje také USB: „Máme téměř hotový útok, který se do auta nahraje připojením telefonu se zlomyslnou aplikací do USB portu v autě,“ uvádí Daan Keuper.

Problém je také v mobilních aplikacích, které někteří výrobci k autům dodávají. „Viděli jsme Nissan Leaf, který má mobilní aplikaci, která posílá HTTPS API dotazy směrem k Nissanu se zprávou: ,odemkni auto s tímto sériovým číslem'. Bez jakékoli autentizace.“ Největší problém připojených aut je, že všechna kromě Tesly nemají možnost aktualizace firmwaru přes internet, takže chyby v nich objevené tam budou ještě nejméně 15 let.

Autor článku

Ondřej Caletka vystudoval obor Telekomunikační technika na ČVUT a dnes pracuje ve vzdělávacím oddělení RIPE NCC, mezinárodní asociaci koordinující internetové sítě.

'; document.getElementById('preroll-iframe').onload = function () { setupIframe(); } prerollContainer = document.getElementsByClassName('preroll-container-iframe')[0]; } function setupIframe() { prerollDocument = document.getElementById('preroll-iframe').contentWindow.document; let el = prerollDocument.createElement('style'); prerollDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:20px;right:25px}"; videoContent = prerollDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('PREROLL sound allowed'); // setUpIMA(true); videoContent.volume = 1; videoContent.muted = false; setUpIMA(); }).catch(function () { console.log('PREROLL sound forbidden'); videoContent.volume = 0; videoContent.muted = true; setUpIMA(); }); } } function setupDimensions() { prerollWidth = Math.min(iinfoPrerollPosition.offsetWidth, 480); prerollHeight = Math.min(iinfoPrerollPosition.offsetHeight, 320); } function setUpIMA() { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Preroll advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = prerollWidth; // adsRequest.linearAdSlotHeight = prerollHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. prerollDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( prerollDocument.getElementById('adContainer'), videoContent); } function unmutePrerollAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } } function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(prerollWidth, prerollHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } function onAdEvent(adEvent) { const ad = adEvent.getAd(); console.log('Preroll event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: if (!ad.isLinear()) { videoContent.play(); } prerollDocument.getElementById('adContainer').style.width = '100%'; prerollDocument.getElementById('adContainer').style.maxWidth = '640px'; prerollDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); if (ad.isLinear()) { intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } prerollDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (prerollLastError === 303) { playYtVideo(); } break; case google.ima.AdEvent.Type.COMPLETE: if (ad.isLinear()) { clearInterval(intervalTimer); } playYtVideo(); break; } } function onAdError(adErrorEvent) { console.log(adErrorEvent.getError()); prerollLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { playYtVideo(); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoPrerollPosition.remove(); playPrerollAd(); } else { return false; } adVolume = 1; return true; } function onContentPauseRequested() { videoContent.pause(); } function onContentResumeRequested() { videoContent.play(); } function onActiveView() { if (prerollContainer) { const containerOffset = prerollContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (prerollPaused) { adsManager.resume(); prerollPaused = false; } return true; } else { if (!prerollPaused) { adsManager.pause(); prerollPaused = true; } } } return false; } function playYtVideo() { iinfoPrerollPosition.remove(); youtubeIframe.style.display = 'block'; youtubeIframe.src += '&autoplay=1&mute=1'; } }
'; document.getElementById('outstream-iframe').onload = function () { setupIframe(); } replayScreen = document.getElementById('iinfoOutstreamReplay'); iinfoOutstreamPosition = document.getElementById('iinfoOutstreamPosition'); outstreamContainer = document.getElementsByClassName('outstream-container')[0]; setupReplayScreen(); } function setupIframe() { outstreamDocument = document.getElementById('outstream-iframe').contentWindow.document; let el = outstreamDocument.createElement('style'); outstreamDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:-5px;right:25px}"; videoContent = outstreamDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; if ( location.href.indexOf('rejstriky.finance.cz') !== -1 || location.href.indexOf('finance-rejstrik') !== -1 || location.href.indexOf('firmy.euro.cz') !== -1 || location.href.indexOf('euro-rejstrik') !== -1 || location.href.indexOf('/rejstrik/') !== -1 || location.href.indexOf('/rejstrik-firem/') !== -1) { outstreamDirectPlayed = true; soundAllowed = true; iinfoVastUrlIndex = 0; } if (!outstreamDirectPlayed) { console.log('OUTSTREAM direct'); setUpIMA(true); } else { if (soundAllowed) { const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('OUTSTREAM sound allowed'); setUpIMA(false); }).catch(function () { console.log('OUTSTREAM sound forbidden'); renderBanner(); }); } } else { renderBanner(); } } } function getWrapper() { let articleWrapper = document.querySelector('.rs-outstream-placeholder'); // Outstream Placeholder from RedSys manipulation if (articleWrapper && articleWrapper.style.display !== 'block') { articleWrapper.innerHTML = ""; articleWrapper.style.display = 'block'; } // Don't render OutStream on homepages if (articleWrapper === null) { if (document.querySelector('body.p-index')) { return null; } } if (articleWrapper === null) { articleWrapper = document.getElementById('iinfo-outstream'); } if (articleWrapper === null) { articleWrapper = document.querySelector('.layout-main__content .detail__article p:nth-of-type(6)'); } if (articleWrapper === null) { // Euro, Autobible, Zdravi articleWrapper = document.querySelector('.o-article .o-article__text p:nth-of-type(6)'); } if (articleWrapper === null) { articleWrapper = document.getElementById('sidebar'); } if (!articleWrapper) { console.error("Outstream wrapper of article was not found."); } return articleWrapper; } function setupDimensions() { outstreamWidth = Math.min(iinfoOutstreamPosition.offsetWidth, 480); outstreamHeight = Math.min(iinfoOutstreamPosition.offsetHeight, 320); } /** * Sets up IMA ad display container, ads loader, and makes an ad request. */ function setUpIMA(direct) { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); if (direct) { adsRequest.adTagUrl = directVast; console.log('Outstream DIRECT CAMPAING advert: ' + directVast); videoContent.muted = true; videoContent.volume = 0; outstreamDirectPlayed = true; } else { adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Outstream advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; } // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = outstreamWidth; // adsRequest.linearAdSlotHeight = outstreamHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function setupReplayScreen() { replayScreen.addEventListener('click', function () { iinfoOutstreamPosition.remove(); iinfoVastUrlIndex = 0; outstreamInit(); }); } /** * Sets the 'adContainer' div as the IMA ad display container. */ function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. outstreamDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( outstreamDocument.getElementById('adContainer'), videoContent); } function unmuteAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } } /** * Loads the video content and initializes IMA ad playback. */ function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(outstreamWidth, outstreamHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } /** * Handles the ad manager loading and sets ad event listeners. * @param { !google.ima.AdsManagerLoadedEvent } adsManagerLoadedEvent */ function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } /** * Handles actions taken in response to ad events. * @param { !google.ima.AdEvent } adEvent */ function onAdEvent(adEvent) { // Retrieve the ad from the event. Some events (for example, // ALL_ADS_COMPLETED) don't have ad object associated. const ad = adEvent.getAd(); console.log('Outstream event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to // determine whether the ad is a video ad or an overlay. if (!ad.isLinear()) { // Position AdDisplayContainer correctly for overlay. // Use ad.width and ad.height. videoContent.play(); } outstreamDocument.getElementById('adContainer').style.width = '100%'; outstreamDocument.getElementById('adContainer').style.maxWidth = '640px'; outstreamDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); // This event indicates the ad has started - the video player // can adjust the UI, for example display a pause button and // remaining time. if (ad.isLinear()) { // For a linear ad, a timer can be started to poll for // the remaining time. intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } outstreamDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (outstreamLastError === 303) { if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } } break; case google.ima.AdEvent.Type.COMPLETE: // This event indicates the ad has finished - the video player // can perform appropriate UI actions, such as removing the timer for // remaining time detection. if (ad.isLinear()) { clearInterval(intervalTimer); } if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } break; } } /** * Handles ad errors. * @param { !google.ima.AdErrorEvent } adErrorEvent */ function onAdError(adErrorEvent) { // Handle the error logging. console.log(adErrorEvent.getError()); outstreamLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { renderBanner(); } } function renderBanner() { if (isBanner) { console.log('Outstream: Render Banner'); iinfoOutstreamPosition.innerHTML = ""; iinfoOutstreamPosition.style.height = "330px"; iinfoOutstreamPosition.appendChild(bannerDiv); } else { console.log('Outstream: Banner is not set'); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoOutstreamPosition.remove(); outstreamInit(); } else { return false; } adVolume = 1; return true; } /** * Pauses video content and sets up ad UI. */ function onContentPauseRequested() { videoContent.pause(); // This function is where you should setup UI for showing ads (for example, // display ad timer countdown, disable seeking and more.) // setupUIForAds(); } /** * Resumes video content and removes ad UI. */ function onContentResumeRequested() { videoContent.play(); // This function is where you should ensure that your UI is ready // to play content. It is the responsibility of the Publisher to // implement this function when necessary. // setupUIForContent(); } function onActiveView() { if (outstreamContainer) { const containerOffset = outstreamContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (outstreamPaused) { adsManager.resume(); outstreamPaused = false; } return true; } else { if (!outstreamPaused) { adsManager.pause(); outstreamPaused = true; } } } return false; } let outstreamInitInterval; if (typeof cpexPackage !== "undefined") { outstreamInitInterval = setInterval(tryToInitializeOutstream, 100); } else { const wrapper = getWrapper(); if (wrapper) { let outstreamInitialized = false; window.addEventListener('scroll', () => { if (!outstreamInitialized) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { outstreamInit(); outstreamInitialized = true; } } }); } } function tryToInitializeOutstream() { const wrapper = getWrapper(); if (wrapper) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { if (cpexPackage.adserver.displayed) { clearInterval(outstreamInitInterval); outstreamInit(); } } } else { clearInterval(outstreamInitInterval); } } }
OSZAR »