Instalujeme WireGuard: návod pro VPN na distribuci Debian

6. 11. 2019
Doba čtení: 8 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
WireGuard sice stále ještě není součástí linuxového jádra, ale to nám vůbec nebrání v jeho nasazení. Ukážeme si, jak WireGuard nainstalovat do Debianu a jak díky němu zprovoznit jednoduchou VPN.

předchozím článku o WireGuardu jsme si představili tuto moderní a jednoduchou VPN, která sídlí v linuxovém jádře. Není sice ještě součástí hlavního vývojového stromu, ale v praxi to nepředstavuje velkou překážku. Budeme instalovat do Debianu, v ostatních distribucích to bude velmi podobné. Část s konfigurací je pak samozřejmě na všech systémech stejná.

Instalujeme do Debianu

Aktuální WireGuard naleznete v Debianu unstable a je připraven ve formě DKMS. Bude vám fungovat i ve starších Debianech: Jessie, Stretch nebo v aktuálním Buster. Instalace je docela přímočará a zabere vám chvilku.

Nejprve je potřeba do stávající instalace přidat repozitář pro unstable. To provedeme tak, že založíme nový soubor /etc/apt/sources.list.d/unstable-wireguard.list a do něj zapíšeme cestu k novému repozitáři:

deb http://deb.debian.org/debian/ unstable main

Nechceme ovšem, abychom z unstable instalovali všechny balíčky, ale jenom ty ručně zvolené. Proto mu snížíme prioritu, aby při výběru vyhrávala naše současná větev. Do souboru /etc/apt/preferences.d/limit-unstable zapíšeme následující tři řádky s pinem (více v samostatném článku):

Package: *
Pin: release a=unstable
Pin-Priority: 90

Máme hotovo, teď už stačí jen aktualizovat seznam balíčků a nechat nainstalovat balíček wireguard. Jelikož ten se nachází jen ve větvi unstable, bude nainstalován automaticky odtamtud. Jde o metabalíček, který sebou pomocí závislostí přiveze balíčky wireguard-dkms a také důležitý wireguard-tools. Pro sestavení jaderného modulu budeme potřebovat ještě hlavičky pro naše jádro.

# apt update
# apt install linux-headers-amd64 wireguard

Tím končí část věnovaná Debianu, zbytek článku už je distribučně nezávislý.

Klíče a IP… to je vlastně vše

WireGuard vyniká především svou jednoduchostí. Nezajímají ho uživatelské účty, certifikáty, autority a podobné věci. Každý komunikující partner je identifikován pomocí veřejného klíče a IP adres, které používá uvnitř VPN. Takhle jednoduché to je.

Volitelně může mít nastavenou také cílovou IP adresu, na kterou je směrována komunikace. WireGuard nezná žádné servery a klienty, všichni komunikující partneři jsou si rovni. Ve skutečném světě ale míváme často jeden koncentrátor a k němu několik komunikujících klientů. Ti pak mají na své straně nastavenou právě cílovou IP adresu centrálního bodu.

V případě komunikace určené k odeslání se v tabulce virtuálních IP adres uvnitř WireGuardu nalezne správný příjemce a jeho veřejný klíč se použije k zašifrování provozu. Ten se pak pošle buď na nakonfigurovanou skutečnou adresu nebo na tu, ze které klient komunikoval naposledy.

Pokud jsou pakety naopak přijímány, použije se místní privátní klíč k jejich dešifrování. Navíc WireGuard automaticky ověří, že odesílatel jako odchozí adresu použil skutečně tu, která mu v rámci VPN náleží. Pokud tomu tak není, pakety jsou odmítnuty. To je velmi užitečná vlastnost, která zajišťuje autenticitu provozu přicházejícího přes WireGuard. Veřejný klíč našeho partnera je pevně svázán s jeho konkrétní adresou (nebo rozsahem) a on si nemůže libovolně vymýšlet jinou nebo se dokonce uvnitř VPN vydávat za někoho jiného.

Manuální konfigurace

Pro konfiguraci se používají běžné síťové nástroje, protože WireGuard maximálně využívá stávajících součástí jádra. Pomocí utility ip tedy můžeme vytvářet nová rozhraní, konfigurovat směrování a dělat vše tak, jak jsme zvyklí i běžných síťových karet.

Pro zbytek konfigurace, specifický právě pro WireGuard, slouží utilita s dvoupísmenným názvem wg, která umí vše podstatné: generovat klíče a upravovat tabulku partnerů. Pomocí ní tedy dostaneme svou VPN do plně funkčního stavu.

Předvedeme si manuální konfiguraci, protože na ní se nejlépe ukazuje celý koncept. Protože ale jde o neperzistentní nastavení, hodí se spíše pro testovací účely. Pro použití v praxi pak lépe poslouží další metody popsané níže.

Nejprve tedy vytvoříme pár klíčů, které budeme na místním stroji používat. Totéž musíme samozřejmě udělat na všech počítačích, které s námi budou komunikovat. Vzájemně si pak vyměníme pouze veřejné klíče. Generování můžeme provést pomocí jednoho řádku, ve kterém vygenerujeme privátní klíč, uložíme ho na disk a rovnou z něj odvodíme klíč veřejný:

# wg genkey | tee wg-private.key | wg pubkey > wg-public.key

Tip: Pokud chcete hezkou adresu, můžete využít nástroj wireguard-vanity-address, který ve velkém generuje náhodné klíče a hledá v nich vámi zadaný řetězec. Pokud jej najde, vypíše vytvořený klíč na standardní výstup. Tahle si můžete připravit pěkné klíče, které pak v sobě třeba nesou název: PETRsAhXGpkLeoayaPQ+Rvk/+k1LR7XggPu­awMFv1X8=

Poté už vytvoříme nové síťové rozhraní, kterému dáme nějaký název (třeba wg0) a jako typ uvedeme wireguard. Pokud hned první krok selže, pak nemáte WireGuard správně nainstalovaný nebo nemáte zavedený potřebný modul.

# ip link add dev wg0 type wireguard

V dalším kroku musíme na rozhraní přidat nějakou IP adresu. Můžeme používat samozřejmě IPv4 i IPv6, adresa by se ale neměla vyskytovat ani v jedné ze skutečných sítí, do kterých jsme my nebo naši partneři připojeni. Nastanou pak potíže se směrováním, protože klienti nebudou vědět, do které sítě dané pakety posílat.

# ip address add dev wg0 192.168.100.1/24

WireGuardu musíme předat klíče, které budou využívány při komunikaci. Na své straně přidáme ten privátní a k protistraně pak jen veřejný. Nejprve začneme našim počítačem, kterému nastavíme UDP port pro přijímání šifrované komunikace a privátní klíč:

# wg set wg0 listen-port 51820 private-key ./wg-private.key

Pro každého partnera, identifikovaného veřejným klíčem, nastavíme povolené IP adresy uvnitř VPN a volitelně také reálnou IP adresu a port.

# wg set wg0 peer TcCK+JJLHZcH9zdLRqtJ7cHiCJH2a6iBN2TjVi6zIxw= \
allowed-ips 192.168.100.2/32 endpoint 192.0.2.123:51820

Tento krok opakujeme pro každého partnera, se kterým budeme chtít komunikovat. Každý dostane svou IP adresu a s ní spojený veřejný klíč.

Posledním krokem je nahození celého rozhraní, čímž to vlastně celé spustíme:

# ip link set wg0 up

Pro přehled o současném nastavení WireGuardu můžeme opět použít příkaz wg, který bez parametrů vypíše informace o všech nakonfigurovaných rozhraních a partnerech:

# wg
interface: wg0
  public key: xcC08j4bC0Z756eokOM1nezhbTU6k25XGSeTmEpVKUQ=
  private key: (hidden)
  listening port: 51820

peer: TcCK+JJLHZcH9zdLRqtJ7cHiCJH2a6iBN2TjVi6zIxw=
  endpoint: 192.0.2.123:51820
  allowed ips: 192.168.100.2/32

Všimněte si, že privátní klíč je v tomto výpise skrytý. Můžete si jej na výslovné přání vypsat pomocí wg show wg0 private-key nebo povolit jeho běžné zobrazování ve výpisu tak, že nastavíte proměnnou prostředí WG_HIDE_KEYS na hodnotu  never.

Můžeme se také přesvědčit, že nám jádro při nahození linky upravilo směrovací tabulku a přidalo do ní správné cesty:

# ip route
…
192.168.100.0/24 dev wg0 proto kernel scope link src 192.168.100.1

Stejný postup musí proběhnout i na druhé straně a pak můžeme začít komunikovat s protistranou pomocí bezpečného tunelu. Zároveň budeme přijímat jen pakety šifrované správným klíčem a používající předpokládanou IP adresu našeho partnera.

Konfigurační soubor

Manuální konfigurace není příliš praktická, existuje však možnost uložit si výše zmíněné konfigurační volby do souboru a z něj je nechat načítat pomocí utility wg-quick. Ta se stará o správu celého rozhraní a umí vlastně provést všechny kroky, které jsme si před chvílí ukazovali.

Formát konfiguračního souboru je pevně dán a je velmi jednoduchý. Všechno pochopíte z následujícího příkladu, který kopíruje námi vytvořenou ruční konfiguraci:

[Interface]
Address = 192.168.100.1/24
PrivateKey = QD8zBS9nCwzhBrr6W9rEtcegvCwRk1SDFZFjSL3bMGQ=

[Peer]
AllowedIPs = 192.168.100.2/32
PublicKey = TcCK+JJLHZcH9zdLRqtJ7cHiCJH2a6iBN2TjVi6zIxw=
Endpoint = 192.0.2.123:51820

Konfigurační soubor pro rozhraní může být uložen kdekoliv, utilita wg-quick ho ale umí pohodlně najít v /etc/wireguard/  a pak vám stačí předat jen název rozhraní. Doporučuji tedy soubor hodit do /etc/wireguard/wg0.conf. Pak bude stačit rozhraní nahodit pomocí:

# wg-quick up wg0

Výhodou konfiguračního souboru je, že je univerzální a lze jej importovat také do WireGuardu na dalších platformách: Android, macOS, iOS nebo Windows.

WireGuard pro road warriory

Předvedli jsme si propojení dvou počítačů pomocí tunelu. Uživatelé si obvykle pod jménem VPN automaticky představují režim, kdy se po světě pohybuje řada uživatelů (road warriors) a ti se potřebují připojovat k jednomu koncentrátoru pevně usazenému ve firmě. Už jsme si řekli, že rozdělení na server a klient ve WireGuardu neexistuje, ale čistě formálně si je takhle můžeme pojmenovat.

Z hlediska konfigurace nejde o nic složitého, vlastně stačí využít všechny výše uvedené postupy, na serveru nakonfigurovat všechny klienty bez pevné vnější adresy (Endpoint) a přidělit jim nějakou IP adresu v naší VPN.

Klienti naopak musí zvolit skutečnou IP adresu serveru a přijímat od něj pakety s libovolnou zdrojovou IP adresou. Tím se server ujme úlohy výchozí brány a začne nám jím proudit veškerý provoz do internetu. O správné směrování komunikace do skutečného internetu se pak postará wg-quick pomocí chytré manipulace s policy routingem. Podstatná část klientské konfigurace pak bude vypadat takto:

zabbix_tip

[Peer]
AllowedIPs = 0.0.0.0/0
PublicKey = xcC08j4bC0Z756eokOM1nezhbTU6k25XGSeTmEpVKUQ=

Vyzkoušejte Cloudflare WARP

Pro rychlé osahání WireGuardu jako VPN klienta je možné použít veřejnou bezplatnou službu Cloudflare WARP, která využívá WireGuard s drobnými úpravami. Pomocí neoficiálního shellového skriptu vygenerujeme pár klíčů, předáme je pomocí API do Cloudflare a obratem obdržíme IP adresy a další konfigurační údaje. Vygenerovaný konfigurační soubor pak můžeme použít k nastartování VPN pomocí nástroje  wg-quick.

$ wget https://gist.github.com/oskar456/594f1b5e84ca887c439fb457800b377e/raw/wgcf.sh
To connect, run as root:
# wg-quick up /home/user/.wgcf/wgcf.conf

To disconnect, run as root:
# wg-quick down /home/user/.wgcf/wgcf.conf

$ sudo wg-quick up /home/user/.wgcf/wgcf.conf
[#] ip link add wgcf type wireguard
[#] wg setconf wgcf /dev/fd/63
[#] ip -4 address add 172.16.0.2 dev wgcf
[#] ip -6 address add fd01:5ca1:ab1e:8027:6691:f8d2:1c5b:413a dev wgcf
[#] ip link set mtu 1400 up dev wgcf
[#] resolvconf -a wgcf -m 0 -x
[#] wg set wgcf fwmark 51820
[#] ip -6 route add ::/0 dev wgcf table 51820
[#] ip -6 rule add not fwmark 51820 table 51820
[#] ip -6 rule add table main suppress_prefixlength 0
[#] ip -4 route add 0.0.0.0/0 dev wgcf table 51820
[#] ip -4 rule add not fwmark 51820 table 51820
[#] ip -4 rule add table main suppress_prefixlength 0

Příště budeme integrovat

V příštím článku se podíváme na to, jak WireGuard integrovat do systému tak, aby například sám nabíhal při startu nebo abyste ho mohli ovládat standardními systémovými nástroji.

Autor článku

Petr Krčmář pracuje jako šéfredaktor serveru Root.cz. Studoval počítače a média, takže je rozpolcen mezi dva obory. Snaží se dělat obojí, jak nejlépe umí.

'; 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 »