[Work/TechInfo/other]

WebGL2(GLSL3)によるTransformFeedbackを用いたGPGPU その2 VTF / 2017-10-20 (金)

VTF - Vertex Texture Fetch

要するに,普通はフラグメントシェーダの中からしか呼べないテクスチャを頂点シェーダで読み込む機能である.

値3つだけでは足りない計算

前項のTransformFeedbackを用いたコードだと,GPGPUをやっている頂点シェーダ(の各スレッド)にはx, y, zの3つの値しか送れない.

uniformで任意の要素数の配列を送るためには,vec4に割り当てるしかなく,vec4変数を大量に宣言,転送しなければならないのでコードが汚くなるし,vec4が256個分が通常の限界らしい.

というわけで,テクスチャに値を入れて,頂点シェーダの中で受け取る方法がVTFである.通常全く問題がない大きさのテクスチャでも16*16*RGBAの4要素分つまり1024個の値を頂点シェーダに一発で送ることができる.現代の普通のマシンなら64*64*RGBAは余裕で送れるはずなので16384個は可能のはず.テクスチャもuniformなので各スレッドで共用の配列として使える.

Intel HD Graphics 5000では頂点シェーダで受け取れるテクスチャの最大枚数が16のようなので,最大16384*16つまり262144個の値を送れるようになる.(ただしOpenGLのテクスチャは定数で管理しているので,1枚増えるごとにコード量は増えることに注意)

ただしテクスチャはあくまでunsgined charであり,頂点シェーダプログラムの中では入力された値が255で割られた値が出てくるので,整数値のラベルを送ったり,1バイト以上の精度が必要な場合は,この方法は難しい.

HTMLファイル

前項と同じくdisplay:noneするcanvasと,計算結果を出力するdivがあるだけ.

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>WebGL2 GPGPU Vertex Texture Fetch</title>

    <script type="text/javascript" src="./webgl2_gpgpu_vtf.js"></script>

  </head>
  <body>

    <canvas id="webgl_canvas"></canvas>
    <div id="result_display"></div>

  </body>
</html>

JavaScriptファイル


//webgl2_gpgpu_vtf.js
'use strict';
window.addEventListener('load', () => {
    // 参考にした: 
    // https://qiita.com/edo_m18/items/3258afe3a1d8ce2c6cd9
    

    if(window.navigator.userAgent.toLowerCase().indexOf('chrome') == -1){
	alert('Currently Only Chrome Browser can allow GLSL3.0(WebGL2)');
    }

    window.glCanvas = document.getElementById("webgl_canvas");
    window.glCanvas.width = 640;
    window.glCanvas.height = 480;
    window.glCanvas.setAttribute("style", "display: none");
    
    const gl2 = window.glCanvas.getContext("webgl2");

    // Vertex Texture Fetchが可能かどうかをチェックする
    let info = gl2.getParameter(gl2.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
    if(info > 0){
	console.log('max_vertex_texture_imaeg_unit: ', info);
	// この値までテクスチャをVertexシェーダ側で読み込める
    }
    else {
	alert('VTF not supported');
	return;
    }

    let vsScriptText = "#version 300 es \n" + 
	"out float result;" + 
	"in vec3 vertexPositions;" + 
	"uniform sampler2D vtfData;" + 
	"const float pixelSize = 2.0;" + 
	"const float frag = 1.0 / pixelSize;" +
	"const float shiftWidth = frag / pixelSize;" + 
	"const float texPosX = 1.0 * frag + shiftWidth;" + 
	"const float texPosY = (1.0 * frag) * frag + shiftWidth;" + 
	"void main(void){" +
             "vec4 valueFromTexture = texture(vtfData, vec2(texPosX, texPosY)).rgba;" + 
              "result = valueFromTexture[0] + valueFromTexture[1] + valueFromTexture[2] + valueFromTexture[3]" +
	" + vertexPositions[0] + vertexPositions[1] + vertexPositions[2];" + 
	"}";
    // uniform sampler2D vtfData; がテクスチャオブジェクト
    // ほんとに頂点座標オブジェクトならtexelFetch()で読み出せるのだが,
    // GPGPUの場合,頂点座標オブジェクトではないので,古典的な方法を使う

    // const float pixelSize = 2.0; //テクスチャの元々の1辺のサイズ
    // 元画素数は2*2だったが,GLSL内部では1.0*1.0に強制変換されるため,
    // 1.0を1辺のサイズで割って,1ピクセル辺りの幅を出している.ここが座標の最小単位になる
    // const float frag = 1.0 / pixelSize;

    // ただしここから0.5ピクセル分の幅ずらして読み込まなければならない
    // これは,ピクセルの真ん中に読み込みカーソル持ってくるという意味
    // ピクセルの端っこだと隣り合ったピクセルと補間されて
    // 「滲んでしまった色」を読み込む可能性があるため
    // float shiftWidth = frag / pixelSize;

    // float texPosX = 座標 * frag + shiftWidth; 
    // float texPosY = (座標 * frag) * frag + shiftWidth;

    // 読み出す座標を決定
    // vec2 readingPixelPos = vec2(texPosX, texPosY);
    // 値の取り出し
    // vec4 values = texture(vtfData, readingPixelPos).rgba;

    // 頂点座標のattrbuteの方も,シェーダプログラム内部できちんと使ってやらないと,
    // Warningを吐く
    
    window.vertexShader = gl2.createShader(gl2.VERTEX_SHADER);
    gl2.shaderSource(window.vertexShader, vsScriptText);
    gl2.compileShader(window.vertexShader);
    console.log(gl2.getShaderInfoLog(window.vertexShader));

    let fsScriptText = "#version 300 es \n void main(void){}";
    window.fragmentShader = gl2.createShader(gl2.FRAGMENT_SHADER);
    gl2.shaderSource(window.fragmentShader, fsScriptText);
    gl2.compileShader(window.fragmentShader);

    window.program = gl2.createProgram();
    gl2.attachShader(window.program, window.vertexShader);
    gl2.attachShader(window.program, window.fragmentShader);


    // TransformFeedbackを行うVertexシェーダ内の変数の登録登録
    gl2.transformFeedbackVaryings(window.program, ["result"], gl2.SEPARATE_ATTRIBS);
    // シェーダのリンク
    gl2.linkProgram(window.program);
    gl2.useProgram(window.program);

    // Vertex Texture Fetchを用いてVertexシェーダに大量の値を渡す
    // GPU内部へテクスチャオブジェクトを生成する
    let vtfDataTexture = gl2.createTexture();
    // 生成したテクスチャオブジェクトをWebGLにバインドする
    //gl2.activeTexture(gl2.GL_TEXTURE0);
    gl2.bindTexture(gl2.TEXTURE_2D, vtfDataTexture);
    // テクスチャの各種パラメータの設定
    // gl.TEXTURE_MAG(MIN) _FILTERにはgl.NEARESTを用いる
    // これはgl.LINEARを使うと頂点と頂点の間で補間されてしまうので,
    // データの受け渡しには向かないから
    gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_MAG_FILTER, gl2.NEAREST);
    gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_MIN_FILTER, gl2.NEAREST);
    gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_WRAP_S, gl2.CLAMP_TO_EDGE);
    gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_WRAP_T, gl2.CLAMP_TO_EDGE);

    // 渡す値の配列を作る
    // Textureの1辺は2の累乗でRGBAでなければならないので,2*2*4 = 16の1次元配列
    let textureWidth = 2;
    let textureHeight = 2;
    let textureSize = textureWidth * textureHeight * 4;
    let vtfData = new Uint8Array(textureSize);
    for(let i=0; i<textureSize; i++){
        // テクスチャは当然Uint8なのだが,
	// Vertexシェーダ内で取り出される時255で除算されて0.0~1.0で出てくる
	// なので,あらかじめ掛け算をしてfloorしておくなど,0〜255の整数で値を入れておく
	vtfData[i] = Math.random() * 255;
    }
    // テクスチャにデータを設定 テクスチャオブジェクトに指定するのではないことに注意
    gl2.texImage2D(gl2.TEXTURE_2D, //Target
		   0, //Mip map level
		   gl2.RGBA, //テクスチャのピクセルフォーマット
		   textureWidth, textureHeight, // width, height
		   0, //Border
		   gl2.RGBA, //ピクセルの配列形式(RGBAの順で並んでいた)
		   gl2.UNSIGNED_BYTE, //1ピクセルのデータ形式(Uint8)
		   vtfData); //


    // 頂点座標の登録とVBOの生成
    let vertexPosArray = 
	new Array(1.0, 2.0, 3,0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
    let numOfPoint = 3; //vec3(x, y, y)で3つ分の配列がある.

    let vboPos = gl2.createBuffer();
    gl2.bindBuffer(gl2.ARRAY_BUFFER, vboPos);
    gl2.bufferData(gl2.ARRAY_BUFFER, new Float32Array(vertexPosArray), gl2.STATIC_DRAW);
    
    let vertexPosAttribLocation = gl2.getAttribLocation(window.program, 'vertexPositions');
    gl2.enableVertexAttribArray(vertexPosAttribLocation);
    let vertexPosAttribStride = 3; //GLSL3.0コードの中でvec3型なので3
    gl2.vertexAttribPointer(vertexPosAttribLocation,
			    vertexPosAttribStride,
			    gl2.FLOAT,
			    false, 0, 0);
    gl2.bindBuffer(gl2.ARRAY_BUFFER, null);
    // 頂点座標の登録とVBOの生成: ここまで


    // TransformFeedbackの指定
    // GPU内へメモリを確保し,登録する
    let vertexTransformFeedback = gl2.createBuffer();
    let transformFeedback = gl2.createTransformFeedback();
    gl2.bindBuffer(gl2.ARRAY_BUFFER, vertexTransformFeedback);
    gl2.bufferData(gl2.ARRAY_BUFFER, numOfPoint * Float32Array.BYTES_PER_ELEMENT, gl2.DYNAMIC_COPY);
    gl2.bindBuffer(gl2.ARRAY_BUFFER, null);

    gl2.bindTransformFeedback(gl2.TRANSFORM_FEEDBACK, transformFeedback);
    gl2.bindBufferBase(gl2.TRANSFORM_FEEDBACK_BUFFER, 0, vertexTransformFeedback);

    // シェーダの実行
    gl2.beginTransformFeedback(gl2.POINTS);
    gl2.drawArrays(gl2.POINTS, 0, numOfPoint);
    gl2.endTransformFeedback();

    // TransformFeedbackの結果を取り出す
    let arrBuffer = new ArrayBuffer(numOfPoint * Float32Array.BYTES_PER_ELEMENT);
    arrBuffer = new Float32Array(arrBuffer);
    gl2.getBufferSubData(gl2.TRANSFORM_FEEDBACK_BUFFER, 0, arrBuffer);

    // 結果を表示する
    document.getElementById('result_display').innerHTML = arrBuffer[0] + "," + arrBuffer[1] + "," + arrBuffer[2];
});
[ ツッコミの受付は終了しています ]
この記事のリンク元 | 5 | 1 |

[Work/TechInfo/other]

まっさらな状態からのMSYS2+GCCでWindows上にC言語とC++の環境を整える / 2017-10-10 (火)

概要

2017年初頭現在では,一番簡単にWindows上にC言語の開発(学習)環境を整える方法が,Unixツール群であるMSYS2をインストールし,MSYS2のパッケージマネージャでgccを入れることである.(clangはまだ多少問題ありとのことでgccを使う.)

MSYS2とパッケージマネージャpacmanを使ったgccのインストール

MSYS2 installerページから,msys2-x86_64-201*(64bit版)もしくはmsys2-i686-201*.exe(32bit版)のどちらかを環境に応じて落として実行.(本記事では64bit版を扱う)

HOME環境変数の事前設定

Windows上の「ユーザ」環境変数HOMEを,自分のWindowsのホームディレクトリを表す「%USERPROFILE%」にしておく(しておかないと,Windowsのホームディレクトリとは独立したホームが作られるので厄介).Windows 10におけるユーザ環境変数設定方法は,

  1. Windowsメニューを右クリック→コントロールパネル→「大きいアイコン」にして→ユーザアカウント→一番左のメニューの下に「環境設定の変数」がある.
  2. 「"ユーザ名"の環境変数」と出てくるので,「新規」ボタン.
  3. 変数名「HOME」,変数値「%USERPROFILE%」を入れておく.

インストール場所

おそらくCドライブ直下が理想的なのだろうが,どこにでもインストールできる.(ただしシンボリックリンクを含んでいるとダメ)

作成されるショートカットの中身

スタートメニューには,「MSYS2 MSYS」,「MSYS2 MinGW 64-bit」,「MSYS2 MinGW 32-bit」の三つが登録されており,それぞれ,MSYS2をインストールした場所の「msys2_shell.cmd」に与えるオプションが違っており,起動時に別の環境変数を与える.

  • 「MSYS2 MSYS」の実態は「msys2_shell.cmd -msys」
  • 「MSYS2 MinGW 64-bit」の実態は「msys2_shell.cmd -mingw64」
  • 「MSYS2 MinGW 32-bit」の実態は「msys2_shell.cmd -mingw32」

である.

MSYS2のパッケージマネージャシステム

pacmanというパッケージ管理コマンドが存在している.2017年年始時点では既に新しいバージョンなので,

$ pacman -Syu

pacman本体と全体の更新,

$ pacman -Sy

で,データベースの更新,

$ pacman -Su

で,インストール済みのパッケージを更新,

$ pacman -S <package_name>

で,指定したパッケージのインストールである.

64bit用MinGWのインストール

pacmanパッケージマネージャを使って,「base-devel」パッケージと,mingw-w64-x86_64-toolchain」パッケージを入れる.

$ pacman -S base-devel mingw-w64-x86_64-toolchain

で入るが,mingw-w64-x86_64-toolchainパッケージのインストールの途中でGCCが対応しているCやC++以外の言語についても入れるか訊いてくる.(base-develはデフォルト=allで全部入れてしまって大丈夫)

:: 16 のパッケージがグループ mingw-w64-x86_64-toolchain にあります:
:: リポジトリ mingw64
   1) mingw-w64-x86_64-binutils  2) mingw-w64-x86_64-crt-git  3) mingw-w64-x86_64-gcc
   4) mingw-w64-x86_64-gcc-ada  5) mingw-w64-x86_64-gcc-fortran  6) mingw-w64-x86_64-gcc-libgfortran
   7) mingw-w64-x86_64-gcc-libs  8) mingw-w64-x86_64-gcc-objc  9) mingw-w64-x86_64-gdb
   10) mingw-w64-x86_64-headers-git  11) mingw-w64-x86_64-libmangle-git
   12) mingw-w64-x86_64-libwinpthread-git  13) mingw-w64-x86_64-make  14) mingw-w64-x86_64-pkg-config
    15) mingw-w64-x86_64-tools-git  16) mingw-w64-x86_64-winpthreads-git

以下のようにCとC++関係のものだけ入れる.

選択して下さい (デフォルト=all): 1 2 3 7 9 10 11 12 13 14 15 16

GCCの実行

MSYS2上で実行するプログラムのコンパイル

先ほどのショートカットから「MSYS2 MinGW 64-bit」(実態は「msys_shell.cmd -mingw64」) を起動して,

$ gcc -v
$ g++ -v

で動くかどうかを確かめる.

Windowsプログラムのコンパイル

上記gccg++コマンドでコンパイルすると,MSYS2環境の中でしか実行できない(MSYS2が提供するdllを必要とする)プログラムが生成される.WindowsのCMD.EXEで動作するツールや,WIN32APIなどのGUIを使うプログラムは,MSYS2環境に依存してはいけないので,mingwコマンドを使い,かつ静的(static)リンクを指定する.

「MSYS2 MinGW 64-bit」ショートカットからシェルを起動し,

$ /mingw64/bin/x86_64-w64-mingw32-gcc Cのファイル名.c -o 実行ファイル名.exe \
-static -static-libgcc

C++の場合は,

$ /mingw64/bin/x86_64-w64-mingw32-g++ C++のファイル名.cpp -o 実行ファイル名.exe \
-static -static-libgcc -static-libstdc++ --std=c++11

とする.(C++11の場合)

MSYS2の設計思想

上記のショートカットというかオプションで分かる通り,MSYS2は起動時のオプションで,

  • 「MSYS2 MSYS」 : UNIX作業環境
  • 「MSYS2 MinGW 64bit」 : 64bit向けバイナリを生成するコンパイラを利用する環境
  • 「MSYS2 MinGW 32bit」 : 32bit向けバイナリを生成するコンパイラを利用する環境(後述)

と3つの状態を切り分けて使う,Windows用としては理想的な設計になっている.

Windows用32bitバイナリをコンパイルするためのmingwは,

pacman -S mingw-w64-i686-toolchain

でインストール可能である.

.minttyrc

msys_shell.cmdはminttyを呼んでいて,実際にユーザが触る端末エミュレータはminttyなので,ホームディレクトリ直下に.minttyrcという設定ファイルを作っておくとよい.

Transparency=high
Locale=ja_JP
Charset=UTF-8
BackspaceSendsBS=yes

上の内容を.minttyrcに書いておくと,日本語がデフォルトになり,かつスケスケ!になる.

[ ツッコミの受付は終了しています ]
この記事のリンク元 | 11 | 1 |

[Work/TechInfo/other]

WebGL2(GLSL3)によるTransformFeedbackを用いたGPGPU / 2017-10-10 (火)

TransformFeedback

Vertexシェーダでどうにかした(行列演算した)後の値をmainプログラムに戻すためのものなのだが,display:noneしたcanvasをwebgl2用として使えば,GPGPUができる.WebCLがまともに動かない現状,JavaScriptでGPGPUを使うためには,これしか方法がない.(ただしChrome限定)

HTMLファイル

display:noneするcanvasと,計算結果を出力するdivがあるだけ.

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>WebGL2 GPGPU Evaluator</title>

    <script type="text/javascript" src="./webgl2_gpgpu_evaluator.js"></script>

  </head>
  <body>

    <canvas id="webgl_canvas"></canvas>
    <div id="result_display"></div>

  </body>
</html>

JavaScriptファイル

//webgl2_gpgpu_evaluator.js
'use strict';
window.addEventListener('load', () => {
    if(window.navigator.userAgent.toLowerCase().indexOf('chrome') == -1){
	alert('Currently Only Chrome Browser can allow GLSL3.0(WebGL2)');
    }

    window.glCanvas = document.getElementById("webgl_canvas");
    window.glCanvas.width = 640;
    window.glCanvas.height = 480;
    window.glCanvas.setAttribute("style", "display: none");
    
    const gl2 = window.glCanvas.getContext("webgl2");
    // 文字列としてGLSLシェーダプログラムを渡す.
    // Vertexシェーダに書く
    let vsScriptText = "#version 300 es \n out float result; in vec3 vertexPositions; void main(void){result = vertexPositions[0] + vertexPositions[1] + vertexPositions[2];}";
    window.vertexShader = gl2.createShader(gl2.VERTEX_SHADER);
    gl2.shaderSource(window.vertexShader, vsScriptText);
    gl2.compileShader(window.vertexShader);

    // こちらはFragmentシェーダ.
    // とりあえず何もしないので,mainの中が空.
    let fsScriptText = "#version 300 es \n void main(void){}";
    window.fragmentShader = gl2.createShader(gl2.FRAGMENT_SHADER);
    gl2.shaderSource(window.fragmentShader, fsScriptText);
    gl2.compileShader(window.fragmentShader);

    window.program = gl2.createProgram();
    gl2.attachShader(window.program, window.vertexShader);
    gl2.attachShader(window.program, window.fragmentShader);

    // TransformFeedbackの登録
    gl2.transformFeedbackVaryings(window.program, ["result"], gl2.SEPARATE_ATTRIBS);

    // シェーダのリンク
    gl2.linkProgram(window.program);
    gl2.useProgram(window.program);

    // 頂点座標の登録とVBOの生成 GPGPUの場合,GLSLへの数値渡しに使う
    // Vertexシェーダ用なので,vec3で受け渡しするのが一番効率が良い?
    // とりあえずこれで3スレッド同時に走ることになる.
    let vertexPosArray = 
	new Array(1.0, 2.0, 3,0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
    let numOfPoint = 3; //vec3(x, y, z)で頂点3つ分の配列がある.つまり3スレッド同時

    let vboPos = gl2.createBuffer();
    gl2.bindBuffer(gl2.ARRAY_BUFFER, vboPos);
    gl2.bufferData(gl2.ARRAY_BUFFER, new Float32Array(vertexPosArray), gl2.STATIC_DRAW);

    let vertexPosAttribLocation = gl2.getAttribLocation(window.program, 'vertexPositions');
    gl2.enableVertexAttribArray(vertexPosAttribLocation);
    let vertexPosAttribStride = 3; //GLSL3.0コードの中でvec3型なので3
    gl2.vertexAttribPointer(vertexPosAttribLocation,
			    vertexPosAttribStride,
			    gl2.FLOAT,
			    false, 0, 0);
    gl2.bindBuffer(gl2.ARRAY_BUFFER, null);
    // 頂点座標の登録とVBOの生成: ここまで

    // TransformFeedbackの指定
    let vertexTransformFeedback = gl2.createBuffer();
    let transformFeedback = gl2.createTransformFeedback();

    gl2.bindBuffer(gl2.ARRAY_BUFFER, vertexTransformFeedback);
    gl2.bufferData(gl2.ARRAY_BUFFER, numOfPoint * Float32Array.BYTES_PER_ELEMENT, gl2.DYNAMIC_COPY);
    gl2.bindBuffer(gl2.ARRAY_BUFFER, null);

    gl2.bindTransformFeedback(gl2.TRANSFORM_FEEDBACK, transformFeedback);
    gl2.bindBufferBase(gl2.TRANSFORM_FEEDBACK_BUFFER, 0, vertexTransformFeedback);

    gl2.beginTransformFeedback(gl2.POINTS);

    // シェーダの実行
    gl2.drawArrays(gl2.POINTS, 0, numOfPoint);
    gl2.endTransformFeedback();

    let arrBuffer = new ArrayBuffer(numOfPoint * Float32Array.BYTES_PER_ELEMENT);
    arrBuffer = new Float32Array(arrBuffer);
    gl2.getBufferSubData(gl2.TRANSFORM_FEEDBACK_BUFFER, 0, arrBuffer);

    document.getElementById('result_display').innerHTML = arrBuffer[0] + "," + arrBuffer[1] + "," + arrBuffer[2];
});
[ ツッコミの受付は終了しています ]
この記事のリンク元 | 7 | 1 |

[Work/TechInfo/other]

iOS11, Android, MacのSafari11, PC版ChromeのそれぞれのWebRTCを使ったカメラ画像入力の指定方法 / 2017-10-10 (火)

いずれも,HTML中のvideoタグ

<video id="local_video" autoplay playsinline></video>

に対して,

window.localVideo = document.getElementById('local_video');

の操作を事前に行なって,localVideoという名前のElementを取得している.(当たり前だが)

iOS11

//for iOS (with both Front and Back Camera)
const medias = {audio: false,
                video: {facingMode : {exact: "environment"}}}; //背面カメラ
navigator.getUserMedia
   (medias,
    function(stream){
        window.localVideo.srcObject = stream;
    },
    function(error){
       alert('navigator.getUserMedia() error:' + error);
    });

Mac版Safari11

// for MacOS Safari (with only 1 Camera)
const medias = {audio: false,
                video: {width:640, 
		height:480}};
navigator.getUserMedia(medias,
                       (stream) => {
		           window.localVideo.srcObject = stream;
		       },
		       (error) => {
		           console.error('navigator.getUserMedia() error:', error);
		       });

Mac版Safari11だとアロー記法による関数定義が有効になっているようだ.iOS11は検証中.

Android版Chrome

// for Android Chrome (with both Front and Back Camera)
const medias = {audio: false,
                video: {width: 640,
		wheight: 480,
		facingMode:{exact: "environment"}}}; //背面カメラ
navigator.mediaDevices.getUserMedia(medias)
  .then(stream => {
       window.localVideo.src = window.URL.createObjectURL(stream);
  })
  .catch(error => {
       console.error('navigator.mediaDvice.getUserMedia() error:', error);
       return;
  });

PC, MacOS版Chrome

// for PC Chrome (with only 1 Camera)
const medias = {audio: false,
                video: {width: 640,
		height: 480}};
navigator.mediaDevices.getUserMedia(medias)
  .then(stream => {
      window.localVideo.src = window.URL.createObjectURL(stream);
  })
  .catch(error => {
      console.error('navigator.mediaDvice.getUserMedia() error:', error);
      return;
  });

ChromeはAndroid,PC版を問わずthen,catchが使える.

[ ツッコミの受付は終了しています ]
この記事のリンク元 | 6 | 1 |

[Work/TechInfo/other]

海外からのSSH接続を禁止したり時間制限をかけるiptablesと自動起動,sshguardの導入 / 2017-03-29 (水)

概要

ipsetを使うと複数のまとまりのIPアドレスをそのまま扱うことができる.

また,http://nami.jp/ipv4bycc/cidr.txt.gzにて,国内のIPアドレス範囲の一覧が公開されている.

これを利用して「ホワイトリスト」を作ることで,国内からのSSHのみ受けることができる.

またiptablesはリブートすると内容が消えてしまうので,最初にスクリプトを走らせてテーブルのruleファイルを生成,さらにsystemdのサービスに登録するための設定ファイルを書いて,systemdに登録しておくと,再起動時も自動的に適用される.

さらにsshguardを導入すると長めの設定時間内に認証開始の接続回数を超えると,かなり長めにブロックし,最終的には永久にブロックすることが可能になる.Ubuntuだとiptablesが入ってればsshguardもインストールするだけで自動的に設定されるので,とりあえず入れておけばかなり安全度が上がる.

以下はUbuntu 16.04LTS用.

iptablesの設定

#!/bin/bash
 
# 国別コードリストをダウンロード
cd /tmp
if [ -s cidr.txt ]; then
    mv cidr.txt cidr.txt.old
fi
 
wget http://nami.jp/ipv4bycc/cidr.txt.gz
gunzip cidr.txt.gz
 
# ホワイトリストを作成
ipset create -exist WHITELIST hash:net
 
# ホワイトリストに日本のIPアドレスを登録
sed -n 's/^JP\t//p' cidr.txt | while read ADDRESS; do
    ipset add WHITELIST $ADDRESS
done

# IPテーブルのセッティング
# ——————
# default setting
# ——————
iptables -P INPUT DROP
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP
iptables -F
iptables -X

iptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

# ——————

# ブラックリストは今回使わないので,コメントアウト
# iptables -A INPUT -m set –match-set BLACKLIST src -j DROP
# HTTP
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
# HTTPS
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# DNS
iptables -A INPUT -p udp --dport 53 -j ACCEPT
# SSH, SFTPなどを国内からの接続に制限する
iptables -A INPUT -p tcp -m tcp --dport 22 -m set --match-set WHITELIST src -j ACCEPT
# SSH, SFTPの接続開始(認証)を300秒に5回までに制限する
iptables -A INPUT -p tcp --syn --dport 22 -m recent --name sshattack --set
iptables -A INPUT -p tcp --syn --dport 22 -m recent --name sshattack --update --seconds 300 --hitcount 5 -j DROP
# そのサーバの機能に必要な必要な範囲を開けて,国内からの接続に制限する
iptables -A INPUT -p tcp -m tcp --dport 60100:60200 -m set --match-set WHITELIST src -j ACCEPT

# 再起動時にiptableのルールを読み込めるように,ファイルに書き出しておく
iptables-save > /etc/iptables.rules

このシェルスクリプトを最初にsudoで実行しておく.すると,/etc/iptables.rulesに設定が吐き出される.

起動時に実行するスクリプトは以下.

#/bin/sh
cd /tmp
wget http://nami.jp/ipv4bycc/cidr.txt.gz -P /tmp
gunzip /tmp/cidr.txt.gz

ipset create -exist WHITELIST hash:net

sed -n 's/^JP\t//p' /tmp/cidr.txt | while read ADDRESS; do
    ipset add WHITELIST $ADDRESS
done

/sbin/iptables-restore < /etc/iptables.rules

最初に実行したスクリプトから,リストをダウンロードしipset WHITELISTを指定する処理を抜き出した部分と,事前に書き出したルールファイルからiptablesを読み込む処理の部分からなる,このシェルスクリプトを/opt/iptables_ssh_jpn_only.shとして作っておき755を与えておく.

[Unit]
Description = SSH allowd only JPN IP

[Service]
ExecStart = /opt/iptables_ssh_jpn_only.sh
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target

これがsystemdの設定ファイルなので/etc/systemd/system/iptables_ssh_jpn_only.serviceとして作っておく.

$ sudo systemctl list-unit-files --type=service

の出力の中に含まれていれば,systemdに認識されている.あとは自動起動の設定をして起動する.

$ sudo systemctl enable iptables_ssh_jpn_only
$ sudo systemctl start iptables_ssh_jpn_only

とすれば,再起動されても自動的にiptablesが読み込まれるようになる.

sshguardのインストール

$ sudo apt install sshguard
$ sudo systemctl enable sshguard
$ sudo systemctl restart sshguard

Ubuntuはiptablesがインストールされていれば,これだけで,

SSHGuardのデフォルトの挙動では、20分間に4回攻撃を検出すると、そのIPアドレスからの接続をブロックします。接続ブロックは一定時間経過すると解除します。但し、接続ブロックから20分以内に再度攻撃を検出すると、ブロック時間を長くしていきます。ブロック回数とブロック時間は以下のようになります。

1回目:420~630秒間の範囲内でブロック
2回目:840~1260秒間の範囲内でブロック
3回目:永久にブロック

SSH総当たり攻撃(辞書攻撃/ブルートフォースアタック)の対策とその効果 - パソコン鳥のブログ

の設定がされる.