技術メモ

神奈川在住のITエンジニアの備忘録。おもにプログラミングやネットワーク技術について、学んだことを自分の中で整理するためにゆるゆると書いています。ちゃんと検証できていない部分もあるのでご参考程度となりますが、誰かのお役に立てれば幸いです。

httpリクエストのHostヘッダ

ApacheやNginxなどのwebサーバには、クライアント(ブラウザ)からのIP直アクセスを検知してアクセス拒否する設定がある、という話を聞いて、

 「ブラウザでホスト名を打ち込んでアクセスしたとしても、(DNSで名前解決されて)最終的にIPアドレスによる通信になるのに、webサーバ側ではどうやってIP直アクセスかどうか判断しているんだろうか?」

という疑問が浮かんだ。そこで、ちょっと調べてみたところ、おそらく webサーバは、httpリクエストのHostヘッダを見て判断しているのだと考えている。

https://jyn.jp/webserver-security_redirect/

IP直アクセスした場合、httpリクエストのHostヘッダにもIPアドレスが入るので、webサーバ側ではそこを見てIP直アクセスだと判断することができるはず。

 

これまで、Hostヘッダについてはあまり意識したことがなかったが、上記以外にも、webサーバでHostヘッダを見てwebサーバ上の適切なサイトへ振り分けするなど、いろいろ使い道があるようだ

http://gihyo.jp/admin/serial/01/eng_knowhow/0018

ファイルを指定したサイズで分割するスクリプト

ファイルを指定したサイズで分割するperlスクリプト (splitFile.pl) を作成した。例えば、巨大なログファイルをエディタで開こうとすると、メモリ不足でまともに見れなくなってしまうので、そんな場合はこのスクリプトで分割してから見ようと思っている。


使い方は以下。
perl splitFile.pl -{size|s} "ファイルを分割するバイト数" "分割対象ファイル"

使用例は以下。
perl splitFile.pl -s 1048576 bigsize.log
(bigsize.log を 1MBごとに分割する)


分割した結果は、ファイルがあるのと同じディレクトリ配下に、1_ファイル、2_ファイル、3_ファイル ・・・ といった感じで出力される。なお、行単位で処理するので、指定したサイズでぴったりで分割されるわけではない。


splitFile.pl

use strict;
use warnings;
use Getopt::Long 'GetOptions';
use File::Basename;

my $maxSize;
GetOptions(
    'size=s' => \$maxSize,
);

if (!$maxSize) {
    die "Need to specify the max size(byte)...";
}

my $maxSizeNum = convertToNum($maxSize);

my $inFile = $ARGV[0];
if (! -f $inFile) {
    die "There is not $inFile file...";
}

my $fileCnt = 1;
my $outFile = createOutputFilePath($inFile, $fileCnt."_");
open(my $outFh, "> $outFile") or die("Error :$!");
open(my $inFh,  "< $inFile")  or die("Error :$!");

my $sizeSum = 0;
my $count = 0;
foreach my $line (<$inFh>) {
    $sizeSum += length($line);
    if($maxSizeNum < $sizeSum) {
        if ($count > 0) {
            # Clear the current output file.
            close($outFh);
            $sizeSum = 0;
            
            # Go to next output file.
            $sizeSum += length($line);
            $fileCnt++;
            $outFile = createOutputFilePath($inFile, $fileCnt."_");
            open($outFh, "> $outFile") or die("Error :$!");
            print($outFh $line);
            $count++;
            next;
        }
    }
    print($outFh $line);
    $count++;
}

print "$count lines are split by $fileCnt files.\n";

close($inFh);
close($outFh);

sub createOutputFilePath {
    my ($inputFilePath, $suffix) = @_;
    my ($name, $dir) = fileparse($inputFilePath);
    my $outputFilePath = $dir . $suffix . $name;
}

sub convertToNum {
    my $maxSize = shift;
    if ($maxSize =~ m/^\d+$/) {
        return $maxSize;
    }
    elsif ($maxSize =~ m/^(\d+)(K|k)$/){
        return $1 * 1024;
    }
    elsif ($maxSize =~ m/^(\d+)(m|M)$/){
        return $1 * 1024 * 1024;
    }
    else {
        die("Invalid input...");
    }
}


ちなみに、例えば Windowsには、ファイルを指定したバイト数で分割する Makecab というコマンドがある。当初はそれを使ってファイル(UTF-8)を分割しようと思っていたのだが、何故だか分割されたファイルの内容がところどころ文字化けするので、上記の perl スクリプトを作成した。Makecab コマンドで文字化けする現象については、幾つか情報が見つかったが、結局、原因は分からなかった。
Makecabコマンドによるファイル分割の問題について | Ask CORE
https://qiita.com/tmd45/items/df6f84d61911f1e814d8

Dockerのコンテナ内の「OS」について

Dockerのコンテナは「アプリケーションの動作環境を提供する」という説明を良く見かける。この「動作環境」について、コンテナ内にはOSの機能の一部があるのか、それとも、仮想マシンのゲストOSのように、コンテナ内には普通のOS(全体)が再現されているのか、ちゃんと理解できずモヤモヤした状態だった。

 

そこで、ネットでいろいろ調べたところ、

なぜDockerではホストOSと違うOSベースのコンテナイメージが動くのか - Qiita

の記事を読んで、だいぶ理解が進んだ。

# 素晴らしい記事をありがとうございました m(_ _)m

 

ざっくり纏めると、コンテナは、OSのカーネル部分は独自で持たずホストOSのものを利用し、ファイルシステムとライブラリは独自のものを持つ、ということ。なので、最初の疑問点に戻ると、Dockerでは「コンテナ内にはOSの機能の一部がある」ということになると思っている。

JavaでSNMPトラップ送信

JavaSNMPトラップを送信するコードを作成した。snmp4j (https://www.snmp4j.org/) を使用しているので、コンパイル&実行には snmp4j-x.x.x.jar が必要。(実機検証は Java1.8 + snmp4j-2.6.2.jar で実施した)


実行方法
java TrapSender [-v {1|2c|2}] [-a agent-addr] [-o trapOid] [-e enterpriseID] destHost


実行例 (SNMPv1 トラップ送信)
java TrapSender -v 1 -a 192.168.0.1 -e .1.2.3.4.5.6 192.168.0.2

実行例 (SNMPv2c トラップ送信)
java TrapSender -v 2c -o .1.2.3.4.5.6 192.168.0.2

import org.snmp4j.*;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import java.io.IOException;

public class TrapSender {

    // Set default value.
    private String destAddr = "127.0.0.1";
    private String agentAddr = "127.0.0.1";
    private int destPort = 162;
    private String trapOid = ".1.2.3.4.5";
    private String enterpriseId = ".1.2.3.4.5";
    private String commName = "public";
    private int snmpVersion = SnmpConstants.version2c;
    private int varBindNum = 3;

    void sendTrap () throws IOException {
        if (snmpVersion == SnmpConstants.version1) {
            sendV1Trap();
        }
        else {
            if (snmpVersion == SnmpConstants.version2c) {
                sendV2cTrap();
            }
        }
    }

    void sendV1Trap () throws IOException {

        // Construct a trap pdu.
        PDUv1 trapPdu = new PDUv1();
        trapPdu.setType(PDU.V1TRAP);
        trapPdu.setEnterprise(new OID(enterpriseId));
        trapPdu.setGenericTrap(PDUv1.ENTERPRISE_SPECIFIC);
        trapPdu.setSpecificTrap(1);
        trapPdu.setAgentAddress(new IpAddress(agentAddr));
        addVarBindsTo(trapPdu, varBindNum);

        // Specify a target(dest info).
        CommunityTarget target = createTarget(commName, SnmpConstants.version1, destAddr, destPort);

        // Send a snmp trap.
        System.out.println("Sending an SNMPv1 trap to " + destAddr + " ...");
        sendPdu(trapPdu, target);
    }

    void sendV2cTrap () throws IOException {

        // Construct a trap pdu.
        PDU trapPdu = new PDU();
        trapPdu.setType(PDU.NOTIFICATION);
        trapPdu.add(new VariableBinding(SnmpConstants.sysUpTime, new TimeTicks(1000)));
        trapPdu.add(new VariableBinding(SnmpConstants.snmpTrapOID, new OID(trapOid)));
        addVarBindsTo(trapPdu, varBindNum);

        // Specify a target(dest info).
        CommunityTarget target = createTarget(commName, SnmpConstants.version2c, destAddr, destPort);

        // Send a snmp trap.
        System.out.println("Sending an SNMPv2c trap to " + destAddr + " ...");
        sendPdu(trapPdu, target);
    }

    private CommunityTarget createTarget(String commName,
                                         int snmpVersion,
                                         String destIpAddr,
                                         int destPort) throws IOException {
        CommunityTarget target = new CommunityTarget();
        target.setCommunity(new OctetString(commName));
        target.setVersion(SnmpConstants.version2c);
        target.setAddress(new UdpAddress(destIpAddr + "/" + destPort));
        return target;
    }

    private void sendPdu (PDU trapPdu, CommunityTarget target) throws IOException {
        TransportMapping transport = new DefaultUdpTransportMapping();
        Snmp snmp = null;
        try {
            transport.listen();
            snmp = new Snmp(transport);
            snmp.send(trapPdu, target);
            snmp.close();
        } catch (IOException ex) {
            throw ex;
        } finally {
            snmp.close();
        }
    }

    private void addVarBindsTo (PDU trapPdu, int varBindNum) {
        if (trapPdu == null || varBindNum == 0) {
            return;
        }
        for (int i=1; i<=varBindNum; i++) {
            String oid = ".1.2.3.4.5." + i;
            String val = "val" + i;
            trapPdu.add(new VariableBinding(new OID(oid), new OctetString(val)));
        }
    }

    void setParams(String[] args) throws IllegalArgumentException {

        for (int i=0; i<args.length; i++) {
            if ("-v".equals(args[i])) {
                i++;
                if ("1".equals(args[i])) {
                    snmpVersion = SnmpConstants.version1;
                }
                else if ("2".equals(args[i]) || "2c".equals(args[i])) {
                    snmpVersion = SnmpConstants.version2c;
                }
                else {
                    throw new IllegalArgumentException("Specified SNMP version is invalid...");
                }
            }
            if ("-a".equals(args[i])) {
                i++;
                if (!isValidIpv4Addr(args[i])) {
                    throw new IllegalArgumentException("Specified agent address is invalid...");
                }
                agentAddr = args[i];
            }
            if ("-o".equals(args[i])) {
                i++;
                if (!isValidOid(args[i])) {
                    throw new IllegalArgumentException("Specified trap oid is invalid...");
                }
                trapOid = args[i];
            }
            if ("-e".equals(args[i])) {
                i++;
                if (!isValidOid(args[i])) {
                    throw new IllegalArgumentException("Specified enterprise id is invalid...");
                }
                enterpriseId = args[i];
            }
            else{
                destAddr = args[i];
            }
        }
        if(destAddr == null) {
            throw new IllegalArgumentException("Trap dest is not specified...");
        }
    }

    private boolean isValidIpv4Addr(String addrStr) {
        String[] octets = addrStr.split("\\.");
        if (octets.length != 4) {
            return false;
        }
        for (String octet : octets) {
            int octetNum;
            try {
                octetNum = Integer.parseInt(octet);
            }
            catch (NumberFormatException ex) {
                return false;
            }
            if (octetNum < 0 || 255 < octetNum) {
                return false;
            }
        }
        return true;
    }

    private boolean isValidOid(String oidStr) {
        final int SUB_ID_CNT_MAX = 128;
        final double SUB_ID_MAX = Math.pow(2,32)-1;
        if (oidStr.startsWith(".")) {
            oidStr = oidStr.substring(1);
        }
        String[] subIds = oidStr.split("\\.");
        if (SUB_ID_CNT_MAX < subIds.length) {
            return false;
        }
        for (int i=0; i<subIds.length; i++) {
            int subIdNum;
            try {
                subIdNum = Integer.parseInt(subIds[i]);
            }
            catch (NumberFormatException ex) {
                return false;
            }
            if (i == 0) {
                if (subIdNum != 0 && subIdNum != 1 && subIdNum != 2) {
                    return false;
                }
            }
            if (subIdNum < 0 || SUB_ID_MAX < subIdNum) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        TrapSender trapsender = new TrapSender();
        try {
            trapsender.setParams(args);
            trapsender.sendTrap();
        } catch (IOException ex) {
            System.err.println("Exception happened while sending an snmp trap...");
            System.err.println(ex.getMessage());
            ex.printStackTrace();
            System.exit(1);
        } catch (Exception ex) {
            System.err.println(ex.getMessage());
            ex.printStackTrace();
            System.exit(1);
        }
    }
}

上記で送信したSNMPトラップをwiresharkで見て、意図した内容になっていたので問題ないはず。
現時点では、トラップに含まれるvarbindなど、ハードコードしている部分もあるので、今後はそれらも引数で変更できるようエンハンスしたい。

ファイルから指定した行間だけを抜き出すスクリプト

ファイルから指定した行間だけを抜き出すスクリプト pickup.pl を作成した。ログファイルなど、非常にサイズの大きいファイルをエディタで見ると、エディタがメモリ不足でまともに動かないことがあるので、必要な行間だけをピックアップして見たい時に使用すると良いと思う。

使い方は、例えば以下のように、ピックアップの開始行、終了行、対象のファイルパスを指定する。
perl pickup.pl -s 1000 -e 2000 bigSize.log
(bigSize.logの1000~2000行目をピックアップする)

ピックアップ結果は、bigSize.log があるディレクトリ配下に pickuped_bigSize.log として作成される。

pickup.pl

use strict;
use warnings;
use Getopt::Long 'GetOptions';
use File::Basename;

my $start;
my $end;
GetOptions(
    'start=s' => \$start,
    'end=s'   => \$end,
);

unless (($start =~/\d+/) and ($end =~/\d+/)) {
    die "Start and end should be digit...";
}

if ($end < $start) {
    die "End should be bigger than start...";
}

my $inFile = $ARGV[0];
if (! -f $inFile) {
    die "There is not $inFile file...";
}
my $outFile = createOutputFilePath($inFile, "pickuped_");

open(my $inFh,  "< $inFile")  or die("Error :$!");
open(my $outFh, "> $outFile") or die("Error :$!");

my $lineNum = 1;
foreach my $line (<$inFh>) {
    if($start <= $lineNum and $lineNum <= $end) {
        print($outFh $line);
    }
    $lineNum++;
}

close($inFh);
close($outFh);

sub createOutputFilePath {
    my ($inputFilePath, $suffix) = @_;
    my ($name, $dir) = fileparse($inputFilePath);
    my $outputFilePath = $dir . $suffix . $name;
}

tarコマンドの戻り値

Linuxの tar コマンドの戻り値には、0, 1, 2 があり、それぞれの意味は以下。

tar(1) - Linux manual page

 

バックアップ・スクリプトなどで tar コマンドによるアーカイブ処理を実行する場合、tarコマンドの戻り値「1」を成功として扱うか失敗として扱うか、迷うかもしれない。

これについては、アーカイブ中にアーカイブ対象のファイルが変更される可能性があり、それが許容される場合は「1」は成功として扱い、そうではない場合は失敗と扱うのが良いと思っている。

例えば、アーカイブ対象の中にログファイルがあり、アーカイブ中にそれが更新されることがある場合は、通常、「1」は成功として扱った方が良いと思う。(そうしないと、バックアップ・スクリプトは頻繁に失敗扱いになってしまうので・・)

Windowsのシャットダウン・スクリプト

Windowsシャットダウン時に確実にサービスを停止させるための手段として「シャットダウン・スクリプト」というものがある。これについて、簡単に纏める。

  • OSの通常のシャットダウン処理 (Service Control Managerによる処理) の前に実行される。
  • シャットダウン・スクリプトの処理が完了するまで、OSの通常のシャットダウン処理に移らない。(ただし、タイムアウトはある)
  • タイムアウトはデフォルトで10分。この時間内に終了しなかったら、強制終了される。

例えば、DBMSのように、停止に時間がかかり、かつOSから強制停止されるとファイルが壊れてその後に障害が起きる可能性があるものなど、停止処理をなるべくちゃんと行いたいサービスについて、このシャットダウン・スクリプトの使用は有効だと思う。

 

以下を参考にさせて頂きましたm(_ _)m

https://tech.nikkeibp.co.jp/it/free/NT/WinReadersOnly/20040415/61/

http://www.atmarkit.co.jp/ait/articles/0407/31/news026.html