A Very Simple Random Level Generation Script for Unity

Randomness is a useful tool in game development to get a lot of content with less work, if utilized correctly. A common use for randomness is level generation.

Random level generation can be implemented in several ways. The following example is kind of faux random level generation, as it uses pre-made “blocks”, akin to rooms, to create one continuous level. For more “true” random generation, I recommend looking into the Unity tutorial on using Cellular Automata for procedural cave generation.

Let’s get to the actual script:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class LevelGenerationController : MonoBehaviour {

    public Transform player;
    public GameObject floor;
    public GameObject[] parts; // level parts used
    public float allowedDistanceUntilEnd = 10f; // if distance from end is lower than this value, moving is triggered 
    public GameObject currentPart;
    private ConnectionPointManager currentConnectionPoints;
    public GameObject nextPart;
    private ConnectionPointManager nextConnectionPoints;

    private int currentPartIndex; // for preventing the use of the same part twice in a row

	// Use this for initialization
	void Start () {
        // find player and floor
        player = GameObject.FindGameObjectWithTag("Player").transform;
        floor = GameObject.FindGameObjectWithTag("Floor");

        // assign the origin level part or block as the current part
        currentPart = floor.transform.FindChild("Origin").gameObject;
        currentConnectionPoints = currentPart.GetComponent<ConnectionPointManager>();

        // pick randomly the next part to be moved to the end of the level
        int nextPartIndex = Random.Range(0, parts.Length);
        nextPart = parts[nextPartIndex];
        nextConnectionPoints = nextPart.GetComponent<ConnectionPointManager>();
        currentPartIndex = nextPartIndex; // cycle indexes
	}
	
	// Update is called once per frame
	void Update () {
        ObservePlayerDistance();
	}

    // move the randomly chosen next part to the end of the level
    // the next part is placed so that it seamlessly joins the previous part
    void MoveNextPart()
    {
        float nextY = currentConnectionPoints.endPoint.position.y - nextConnectionPoints.beginPoint.localPosition.y;
        nextPart.transform.position = new Vector3(currentPart.transform.position.x + 50, nextY, 0);
    }

    void ObservePlayerDistance()
    {
        // if the player is close to the end of the level (or more than allowed)
        if (currentConnectionPoints.endPoint.position.x - player.transform.position.x < allowedDistanceUntilEnd)
        {
            MoveNextPart(); // move next part to the end of the level
            CycleParts(); // assign the moved part to the current part and choose a new next part
        } 
    }

    void CycleParts()
    {
        currentPart = nextPart;
        currentConnectionPoints = nextConnectionPoints;

        // choose a random number and use it as index to pick a new part
        int nextPartIndex = Random.Range(0, parts.Length);
        // prevent the same part from being used twice
        while (nextPartIndex == currentPartIndex)
        {
            nextPartIndex = Random.Range(0, parts.Length);
        }
        nextPart = parts[nextPartIndex];
        currentPartIndex = nextPartIndex; // cycle indexes
        nextConnectionPoints = nextPart.GetComponent<ConnectionPointManager>();
    }
}

So what is actually going on there? The process is rather simple (hence the name of the post).

  1. During Start(), the script looks for the player and floor (requires a GameObject with tag “Floor” to exist), after which the current part is assigned and next part is chosen randomly from the collection of given parts, from which to build the level from.
  2. During Update(), the player character’s position is observed. If the player character is closer to end of the level than allowedDistanceUntilEnd specifies, MoveNextPart() is run. This moves the randomly chosen part to the end of the level, to a position in which it will seamlessly connect to the previous part. Determining what that position is what I will discuss later in this post.
  3. In Update(), after MoveNextPart() has been run, the parts are cycled in CycleParts(). Here the script assigns the previously moved “next part” as the current part and picks a new next part.

Now, to answer how the next position is determined: the controller is told where the beginning points and end points of the current and next parts are. These beginning points and end points are GameObjects that are placed to the end and beginning coordinates of each part or “block”. Each part or “block” also has a script called ConnectionPointManager, which search for the start and end points when the scene is loaded. Basically, the functioning of this script depends on the correct placement of these beginning and end points.

Example of the correct placement of the Beginning Point.
Example of the correct placement of the Beginning Point.

Here is what the ConnectionPointManager looks like:

using UnityEngine;
using System.Collections;

public class ConnectionPointManager : MonoBehaviour {

    public Transform beginPoint;
    public Transform endPoint;

	// Use this for initialization
	void Start () {
        Transform[] points = GetComponentsInChildren<Transform>();
        foreach (Transform point in points)
        {
            if (point.tag.Equals("Begin Point"))
            {
                beginPoint = point;
            }
            if (point.tag.Equals("End Point"))
            {
                endPoint = point;
            }
        }
	}
}

In Start(), all Transform components are collected from the child GameObjects of the part or “block”, after which they are looped through. Depending on the tag of the GameObject, the Transform is assigned to either beginPoint or endPoint.

Beginning and End Points as child objects of "Blocks".
Beginning and End Points as child objects of “Blocks”.

If you want to try out the code, you can download the UnityPackage with all the assets and a functioning test scene from here.

All the visual assets are the creations of Kenney. You can download the asset pack (Platformer Pack Redux) from here.

Simple Player Controller Script In Unity

Possibly the simplest game genre to make is the 2D platformer (by now somewhat infamous for that). Still, the character controller for the player character in a 2D platformer can be tricky to implement.

What makes this particular rather simple script be more complicated is the Finite State Machine that it implements. At this point, the state machine prevents player from jumping in mid-air, or basically prevents the player character from existing in multiple states simultaneously. The state machine however makes expansion of the character’s capabilities simple, if such is desired. For instance, if the player character should be able to slide, a slide state simply is made, and all slide related functionality is added to the state.

The Player Controller:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[RequireComponent(typeof(Rigidbody2D))]
public class SimplePlayerController : MonoBehaviour {

    public Rigidbody2D myRigidbody { get; private set; }
    [SerializeField] private LayerMask floorLayer;
    
    private IState currentState;
    private Dictionary<string, IState> states;
    public static string IdleState = "Idle";
    public static string RunState = "Run";
    public static string JumpState = "Jump";

    private float horizontalInput; // by default, A and D keys
    private float movement; // the actual movement, movementSpeed multiplied by horizontalInput
    public float movementSpeed = 20f; // the speed multiplier
    public float jumpVelocity = 30f; // the velocity value that is added to the current velocity vector when player jumps

	// Use this for initialization
	void Start () {
        myRigidbody = GetComponent<Rigidbody2D>();

        // instantiate dictionary and add states
        states = new Dictionary<string, IState>();
        states.Add(IdleState, new PlayerIdleState());
        states.Add(RunState, new PlayerRunState());
        states.Add(JumpState, new PlayerJumpState());

        if (floorLayer == 0) // if floor layer has not been set in the inspector, set it to layer named Floor
        {
            floorLayer = LayerMask.GetMask("Floor");
        }

        // set the current state to Idle
        currentState = states[IdleState];
        currentState.OnEnter(this);
	}
	
	// Update is called once per frame
	void Update () {
        horizontalInput = Input.GetAxis("Horizontal"); // collect horizontal Input
        if (currentState is PlayerRunState) // multiply movementSpeed with horizontalInput if Running
        {
            movement = movementSpeed * horizontalInput;
        }
        currentState.OnUpdate(this); // state update
	}

    void FixedUpdate()
    {
        myRigidbody.velocity = new Vector2(movement, myRigidbody.velocity.y);
    }

    public void Transition(string nextState)
    {
        IState endState = states[nextState];
        endState.OnEnter(this);
        currentState.OnExit(this);
        currentState = endState;
    }

    // check if player is on ground
    public bool IsGrounded()
    {
        // cast a 2D ray downwards with distance of 2f, accept collision only if the object is on floor layer
        if (Physics2D.Raycast(transform.position, Vector2.down, 2f, floorLayer)) 
        {
            Debug.Log("Is on ground");
            return true;
        }
        return false;
    }

    // called from outside the controller, from the jump state (when entered)
    public void Jump()
    {
        myRigidbody.velocity += new Vector2(0, jumpVelocity);
    }
}

The State interface that is implemented by all Player States:

public interface IState
{
    void OnUpdate(SimplePlayerController fsm);
    void OnEnter(SimplePlayerController fsm);
    void OnExit(SimplePlayerController fsm);
}

The Player Idle State:

using System;
using UnityEngine;

public class PlayerIdleState : IState
{
    public void OnUpdate(SimplePlayerController fsm)
    {
        if (Mathf.Abs(Input.GetAxis("Horizontal")) > 0.0001f)
        {
            fsm.Transition(SimplePlayerController.RunState);
        }
        if (Input.GetButtonDown("Jump") && fsm.IsGrounded())
        {
            fsm.Transition(SimplePlayerController.JumpState);
        }
    }

    public void OnEnter(SimplePlayerController fsm)
    {
        Debug.Log("Idle State Enter");
    }

    public void OnExit(SimplePlayerController fsm)
    {
        Debug.Log("Idle State Exit");
    }
}

The Player Run State:

using System;
using UnityEngine;

public class PlayerRunState : IState
{
    public void OnUpdate(SimplePlayerController fsm)
    {
        if (Mathf.Abs(Input.GetAxis("Horizontal")) < 0.0001f)
        {
            fsm.Transition(SimplePlayerController.IdleState);
        }
        if (Input.GetButtonDown("Jump") && fsm.IsGrounded())
        {
            fsm.Transition(SimplePlayerController.JumpState);
        }
    }

    public void OnEnter(SimplePlayerController fsm)
    {
        Debug.Log("Run State Enter");
    }

    public void OnExit(SimplePlayerController fsm)
    {
        Debug.Log("Run State Exit");
    }
}

The Player Jump State:

using System;
using UnityEngine;

public class PlayerJumpState : IState
{
    public void OnUpdate(SimplePlayerController fsm)
    {
        if (fsm.IsGrounded())
        {
            // transition back to idle when back on ground
            fsm.Transition(SimplePlayerController.IdleState);
        }
    }

    public void OnEnter(SimplePlayerController fsm)
    {
        fsm.Jump();
        Debug.Log("Jump State Enter");
    }

    public void OnExit(SimplePlayerController fsm)
    {
        Debug.Log("Jump State Exit");
    }
}

You can download all the code and a sample level from here.
All the amazing art that is in the package is done by Kenney. You can download his Platformer Asset Pack Redux from here.

A Very Simple Movement Script In Unity

A good way to explain the basics of Unity scripting is to make a very simple script that simply takes the A and D or left and right arrow keys inputs and moves the gameobject with those values. Let’s take a look at the following example.


using UnityEngine;
using System.Collections;

namespace PlatformerExample.Scripts {

   [RequireComponent(typeof(Rigidbody2D))]
   public class SimpleMovementController : MonoBehaviour {

      private Rigidbody2D m_Rigidbody;
      private float horizontalInput;
      [SerializeField] private float movementSpeedMultiplier = 20f;

      // Use this for initialization
      void Start ()
      {
         m_Rigidbody = GetComponent<Rigidbody2D>();
      }

      // Update is called once per frame
      void Update ()
      {
         // Collect inputs here
         horizontalInput = Input.GetAxis("Horizontal");
      }

      // Do physics related operations here
      void FixedUpdate()
      {
         // Now the input values are used for altering the velocity of the rigidbody
         m_Rigidbody.velocity = new Vector3(
            horizontalInput * movementSpeedMultiplier,
            m_Rigidbody.velocity.y,
            0
         );
      }
   }
}

At the top of the file, we import our libraries. Most important of these is the UnityEngine -library. Without it, we don’t have access to Unity Engine’s various classes.

Next we have the line with RequireComponent. This is used to prevent situations in which there isn’t a RigidBody 2D -component attached to the gameobject. The Unity editor prevents this script from being attached to a gameobject without a RigidBody 2D -component.

In the Start() -method we assign a reference of the RigidBody 2D -component that is attached to the gameobject to the m_RigidBody variable.

In Update() -method, which is run every frame, we read the inputs. In this case, we want the horizontal inputs, in other words the inputs from whatever controls are bound to the horizontal axis. By default, these are A, D, left arrow and right arrow keys on the keyboard. We store the value of this axis to horizontalInput. The limits for this value are -1 and 1, so the values are always something from between. When left arrow is pressed down, the value is -1, and for right arrow it is 1.

We then apply this value to the velocity of the RigidBody in the FixedUpdate() -method. Unlike the Update() -method, FixedUpdate() isn’t dependent on the framerate, but rather operates based on real world time, which makes it ideal for physics related tasks. In the script, a new Vector3 is assigned on every loop to RigidBody.velocity. The Vector3 consists of 3 values: X, Y and Z. As this script is for 2D, we ignore the Z -axis, and assign a 0 to it. The X value is the horizontal input multiplied by the movement speed multiplier, which gives the movement the necessary speed increase. The Y value is the current Y-axis velocity. If that is not assigned to Y value, it causes a “floaty” effect. That is due to the effect of gravity being overwritten. So to retain the effect of gravity, we assign the current Y-axis velocity to the new Y-axis velocity.

That is the script “in a nutshell”.

You can download all the code and a sample level from here.
All the amazing art that is in the package is done by Kenney. You can download his Platformer Asset Pack Redux from here.

Nginx asennus ja sen käyttäminen käänteisproxyna

Viikkoharjoitus 5 jatko (f), Linux palvelimena -kurssi: http://terokarvinen.com/2012/aikataulu-linux-palvelimena-ict4tn003-7-ict4tn003-kevaalla-2013

Jatkoa aikaisempaan harjoitukseen. Asennan Nginxin ja käytän sitä käänteisproxyna.

Testikoneena toimii sama kone kuin aikaisemmissa raporteissa, eli vanha HP:n t650.fi.

Asennus

Lopetin Apachen ja Varnishin ensiksi

$ sudo service apache2 stop
$ sudo service varnish stop

sitten asensin Nginxin

$ sudo apt-get install nginx

Käynnistin Nginxin komenolla

$ sudo /etc/init.d/nginx start

ja localhost antoi sivun, missä luki “Welcome to nginx!” Käynnistin Apache2 uudelleen, koska olin jo aikaisemmin säätänyt sen kuuntelemaan eri porttia (8080).

Säätäminen käänteisproxyksi

Haluan nginxin tarjoamaan staattisia versioita Apachen tarjoamista dynaamisista sivuista. Tällä kertaa muokkasin default -tiedostoa /etc/nginx/sites-enabled/ -hakemistossa. Jos sivusi nimi on esimerkiksi techdemos.com, muokkaat techdemos.conf -tiedostoa.

default -tiedostossa kohta, jota haluan muokata:

nginx muokattava kohta

Muokkaukset:

nginx default muokkaukset

Tulokset

Ilman nginxiä käänteisproxyna:

nginx without

Tulos nginx käänteisproxyna:

nginx with

 

Apuna harjoituksessa:

http://tumblr.intranation.com/post/766288369/using-nginx-reverse-proxy

Varnish ja AB (apache bench) testaus

Viikkoharjoitus 5, Linux palvelimena -kurssi: http://terokarvinen.com/2012/aikataulu-linux-palvelimena-ict4tn003-7-ict4tn003-kevaalla-2013

Tällä viikolla asennan Varnishin nopeuttaakseni omien web-sivujen latautumista. Testaan vaikutukset AB:lla (apache bench).

Testikoneena toimii sama kone kuin aikaisemmissa raporteissa, eli vanha HP:n t650.fi.

Alkutestaus

Katsotaan ensiksi kuinka hyvin Apache tällä hetkellä avaa sivut. Käytämme sivuna WordPress blogia. Asensin sisällönhallintapalvelun uudelleen userdir:iin, joten osoitteena on nyt 127.0.0.1/~uusiott/wordpress. Annetaan Apache:lle 100 pyyntöä kerralla:

$ ab -n 100 127.0.0.1/~uusiott/wordpress

Tuloksena oli erittäin pienet ajat.

ab n 100

Tulokset eivät olleet luotettavia, joten lisäsin pikku hiljaa vaativuutta. Sain luotettavia tuloksia kyseisillä asetuksilla:

$ ab -n 4000 -c 200 127.0.0.1/~uusiott/wordpress

Tulokset:

ab n 400 c 200

Varnish asennus ja käyttöönotto

Varnish asennetaan kuten melkein kaikki softa:

$ sudo apt-get install varnish

Seuraavaksi tulikin haastavampi osuus: Varnish pitää saada kuuntelemaan porttia 80 ja siirtämään tiedot eteenpäin Apachelle, joka käyttää eri porttia. Eli Apachen ports.conf -tiedostoa ja Varnishin /etc/default/ -hakemistossa olevaa varnish -tiedostoa pitää muokata. Samoin Varnishin omassa /etc/varnish/ -hakemistossa olevaa default.vcl -tiedostoa. Ensiksi kuitenkin sammutetaan apache:

$ sudo service apache2 stop

ja suljetaan portti 80:

$ sudo ufw deny 80

Tämän jälkeen ensiksi menin /etc/default hakemistoon säätämään varnish -tiedostoa. Kohta mitä piti muokata oli tämä:

default varnish conf

Muokattuna se näyttää tältä:

default varnish conf after

Eli portti vaihdettiin 6081:stä 80:een. Seuraavaksi menin /etc/varnish -hakemistoon muokkaamaan default.vcl -tiedostoa. Tämä ei ole pakollista, koska valmiina oleva portti 8080 on hyvä. Jos haluaa käyttää jotain muuta porttia siirtämään Apachelle dataa, sen voi nyt säätää. Elikkä säädettävä kohta:

default varnish port conf

.port -kohta kyseessä siis.

Vielä Apachen ports.conf pitää säätää. Muokattava kohta on heti alussa:

apache ports conf

Listen 80 pitää vaihtaa Listen 8080, tai siihen minkä on säätänyt /etc/varnish/default.vcl -tiedostossa. Jätin sen default.vcl tiedostossa 8080, joten muokkaan ports.conf -tiedostoa siten, että ensimmäisessä Listen kohdassa lukee Listen 80 sijaan Listen 8080. Tämän jälkeen Apache kuuntelee porttia 8080.

Testaus Varnishin käyttöönoton jälkeen

Ajoin saman komennon kuin aikaisemmin:

$ ab -n 4000 -c 200 127.0.0.1/~uusiott/wordpress

Tulokset:

ab n 4000 c 200 after

Eli aika mikä testiin meni laski noin sekunnilla, vastattujen pyyntöjen määrä sekunnissa nousi noin 1200:sta noin 2000:een ja aika yhden pyynnön vastaamiseen laski noin 50 millisekuntia.

Koko toimenpiteeseen meni noin puoli tuntia.

Jos haluaa, että Varnish pistä joitain sivuja ja tiedostoja cacheen, se pitää säätää default.vcl -tiedostossa (/etc/varnish). Tiedostoon pitää kirjoittaa:

if (req.url ~ "^/*hakemisto*\.*tiedostopääte*$) {
   return pass;
}

Tietenkin ilman * -merkkejä. Esimerkiksi jos kyseessä on levykuvia, jotka ovat hakemistossa images:

if (req.url ~ "^/images\.iso$) {
   return pass;
}

Apuna toimi:

Suhteellisen hyvä Varnish asennus opas, jota käytin tunnilla: http://www.euperia.com/website-performance-2/setting-up-varnish-with-apache-tutorial/299

Varnishin säätäminen usealle web-palvelimelle: http://www.lullabot.com/articles/varnish-multiple-web-servers-drupal

Sisällönhallinta Linuxilla

Viikkoharjoitus 4, Linux palvelimena -kurssi: http://terokarvinen.com/2012/aikataulu-linux-palvelimena-ict4tn003-7-ict4tn003-kevaalla-2013

Tällä viikolla asennan WordPress palvelun koneelle, jossa on Apache 2 web-palvelin ja MySQL -tietokantapalvelin.

Testikoneena toimii sama kone kuin aikaisemmassa raportissa, eli vanha HP:n t650.fi.

Valmistelut

Tarvitsen WordPress ohjelmiston, minkä saa wordpress.org -sivustolta. Latasin WordPress tar.gz -tiedoston. Purin sen.

$ tar -xvf wordpress-3.5.1.tar.gz

Kopioin kansion /var/www/ -hakemistoon:

$ sudo mkdir /var/www/wordpress

$ sudo cp -r Downloads/wordpress/* /var/www/wordpress

SQL -palvelimen säätö

Ensiksi piti luoda tietokanta WordPress -sisällönhallintapalvelimelle. Yritin kirjautua MySQL:ään, mutta olin unohtanut salasanan. Tähän auttoi

$ sudo dpkg-reconfigure mysql-server-5.5

Kun olin säätänyt dialogeissa salasanan uudelleen, kirjauduin sisään MySQL -palveluun root -käyttäjänä

$ mysql -u root -p

-p saa mysql:n pyytämään salasanaa.

MySQL:ssä pääsin tekemään uutta tietokantaa. Tämän jälkeen loin uuden käyttäjän ja annoin käyttäjälle kaikki oikeudet uuteen kantaan. Kaikki SQL-lauseet alapuolella

CREATE DATABASE wp_otto;

CREATE USER ‘ottouusihakala’@’localhost’ IDENTIFIED BY ‘tämäeiolesalasanani’;

GRANT ALL ON wp_otto.* TO ‘ottouusihakala’@’localhost’;

Kun tietokanta oli valmis päästin seuraavaan vaiheeseen.

WordPress -palvelun asentaminen

Pistin selaimeen 127.0.0.1/wordpress osoitteeksi. Eteen tuli sivu jossa ilmoitetaan, että wp-config.conf -tiedosto puuttuu.

wordpress no config

Asennusprosessi käynnistyy, kun painaa Create a Configuration File. Sitä kun painaa pääsee vastaavanlaiselle sivulle:

wordpress lets go

Painoin Let’s Go!, ja pääsin sivulle, missä pyydettiin tietokannan tietoja.

wordpress db info

Täytin lomakkeen luomani tietokannan tiedoilla. Table prefix -kohtaan pistin x -kirjaimen eteen. Tiedot olivat oikein ja yhteys tietokantaan onnistui, mutta wordpress ei onnistunut tekemään wp-config.php -tiedostoa itse, joten joudun tekemään sen wordpressin puolesta. Kentässä on kaikki koodi:

wordpress config copy paste

sen kun kopioi ja liittää

$ sudo nano /var/www/wordpress/wp-config.php

ja tallentaa, voi siirtyä eteenpäin asennuksessa. Seuraavassa lomakkeessa täytin laitoin käyttäjätunnuksen ja salasanan. Tärkeätä: pidä rasti ruudussa lomakkeen lopussa, muuten sivua ei voi löytää netistä esim. googlen avulla.

wordpress user info

Install WordPress painike vei sivulle, missä minulle ilmoitettiin asennuksen onnistuneen, ja tarjottiin Log In -painiketta. Kirjauduin sisään palveluun.

Säädin asetuksista permalinks -asetuksia. Laitoin linkkien noudattavan muotoilua päivämäärä/nimi/. Tämän jälkeen piti päivittää .htaccess -tiedosto, mutta sitä ei ollut, joten tein kyseisen tiedoston. Kopioin ja liitin wordpressin antaman koodin tiedostoon ja tallensin. Itse tiedoston tallensin wordpress kansioon.

Apache 2 ja nimipohjainen virtuaalipalvelin

Viikkoharjoitus 3, Linux palvelimena -kurssi: http://terokarvinen.com/2012/aikataulu-linux-palvelimena-ict4tn003-7-ict4tn003-kevaalla-2013

Otsikossa mainitaankin, että ohjelmistona tällä kertaa on Apache 2. Raportissa laitetaan nimipohjainen virtuaalipalvelin (name based virtual host) toimimaan.

Raportissa myös analysoidaan virheviestejä Apachen lokista.

Testikoneena toimii sama kone kuin aikaisemmassa raportissa, eli vanha HP:n t650.fi.

Oletan, että Apache on jo asennettu. Oletan myös, että URLit ovat jo hankittu.

Nimipohjainen virtuaalipalvelin

Aloitan luomalla sivujen nimiä vastaavat hakemistot /var/www/ – hakemistoon. Esimerkkinä olkoot sivut http://www.jaakkomakila.fi ja http://www.machinefixes.fi (sivujen ei ole milläkään tavalla tarkoitus vastata olemassa olevia sivuja).

$ sudo mkdir /var/www/jaakkomakila

$ sudo mkdir /var/www/machinefixes

Seuraavaksi Apachen .conf -tiedostoja. Kyseisen tiedostot löytyvät /etc/apache2/ -hakemistosta. Muokkasin ensiksi httpd.conf -tiedostoa.

$ sudo nano httpd.conf

Raportin lopussa olevasta linkistä pääsee Apache 2 dokumentaatiosivuille, josta voikin löytää tiedot siitä, miten kyseistä tiedostoa tulee muokata. Tässä on kuitenkin esimerkkisivuillemme httpd.conf muokkaukset:

apache2 httpd.conf

Huomaa, että kyseiset tiedot ovat esimerkkejä.

Nyt Apache käyttää kahta URL:ia yhdellä IP-osoitteella.

Suositeltavaa on laittaa myös ServerAlias httpd.conf -tiedostoon <VirtualHost *:80> alle. Esimerkiksi jos ServerAlias on

jaakkomakila.fi *.jaakkomakila.fi

vierailija voi kirjoittaa selaimeen jaakkomakila.fi ja hän silti pääsee sivulle, vaikka häneltä puuttuu www edestä.

Virheviestejä

Tutkin Apache 2 virhelokia (/var/log/apache2/error.log), ja löysin joitakin tavallisia virheviestejä:

[Sun Feb 03 15:19:09 2013] [error] [client 10.0.0.3] File does not exist: /var/www/favicon.ico

Elikkä favicon.ico tiedostoa ei ole hakemistossa /var/www/.

[Sun Feb 03 15:51:27 2013] [error] [client 10.0.0.3] File does not exist: /var/www/~uusio

Väärin kirjoitettu URL. URL jonka kirjoitin oli ottouusihakala.no-ip.org. Kyseisellä osoitteella Apache2 hakee tiedostot /var/www/ -hakemistosta, eikä kotihakemistosta (user directory).

Hyödyllisiä linkkejä

Linkki Apache 2 dokumentaatioon virtuaalipalvelimista: http://httpd.apache.org/docs/2.2/vhosts/

Ilmainen DNS palvelu dynaamisia IP-osoitteita varten: http://www.noip.com/

Sleuth Kit ja rootkitin jäänteiden etsiminen

Viikkoharjoitus 2, Linux palvelimena -kurssi: http://terokarvinen.com/2012/aikataulu-linux-palvelimena-ict4tn003-7-ict4tn003-kevaalla-2013

Tässä raportissa käsitellään rootkitin jäännösten etsimistä. Työkaluna on Sleuth Kit.

Levykuva on sivulta: http://old.honeynet.org/scans/scan15/.

Testikoneena toimii sama kone kuin aikaisemmassa raportissa, eli vanha HP:n t650.fi.

En varmuuskopioinut järjestelmää, koska ei ollut yhtään turvattavia tiedostoja. Koko kovalevy oli formatoitu viikko sitten.

Latasin ensiksi Sleuth Kitin:

$ sudo apt-get sleuthkit

Jonka jälkeen latasin Scan Of The Month -sivuilta edellä mainitun levykuvan (honeynet.tar.gz). Navigoin Downloads -kansioon, jossa purin tiedoston:

$ tar -xvf honeynet.tar.gz

Tar.gz -tiedoston voi myös purkaa ihan graafisessa käyttöliittymässä.

Tässä vaiheessa tarkistin tiedostojen honeynet.tar.gz ja honeypot.hda8.dd MD5 -tiivisteet. Vertasin niitä sivuilla annettuihin tiivisteihin. Tiivisteet olivat 100 % identtisiä. Tässä vielä itse tiivisteet:

honeynet.tar.gz: 0dff8fb9fe022ea80d8f1a4e4ae33e21

honeypot.hda8.dd: 5a8ebf5725b15e563c825be85f2f852e

Kopioin honeypot.hda8.dd -levykuvan kotihakemistossa olevaan t/ -hakemistoon.

Seuraavaksi ajoin komennot:

$ tsk_recover honeypot.hda8.dd unallocated

$ tsk_recover -a honeypot.hda8.dd allocated

En vielä tee mitään kansioitten sisällöllä. Tein kansion output/ mihin tallennan sleuth kitin tuottamat tulokset.

Tutkiminen

Tein ils -komennolla listan metadata rakenteista. -m asetus tekee tulosteesta mactime -yhteensopivan. Tallensin tulokset output/ -hakemistoon:

$ ils -m honeypot.hda8.dd > output/hda8.ils

Tämän jälkeen ajoin tsk_gettimes -komennon (sama kuin fls -m):

$ tsk_gettimes honeypot.hda8.dd > output/gettimes_result.txt

Tätä tiedostoa käytin mactime -komennon kanssa luodakseni listan tapahtumista järjestelmässä:

$ mactime -b output/gettimes_result.txt 2001-03-15 > output/times2001-03-15.txt

$ mactime -b output/gettimes_result.txt 2001-03-14 > output/times2001-03-14.txt

$ mactime -b output/gettimes_result.txt 2001-03-13 > output/times2001-03-13.txt

Mactime -komennolle pitää määritellä vuoden päivä, miltä se poimii tiedot. Otin varmuuden 14. ja 15. päivien lisäksi listan 13. päivän tapahtumista. Less -työkalulla tuloksia tutkiessani huomasin, että kaikki sisälsivät samat tiedot, elikkä päivien 15. – 16. tapahtumat.

Seuraavaksi ajoin mactime -komennon hda8.ils -tiedostolle.

$ mactime -b output/hda8.ils 2001-03-15 > output/ils_times2001-03-15.txt

Ils ja fls tulosten tutkiminen

Avasin less -työkalulla times2001-03-15.txt -tekstitiedoston. Painoin end -näppäintä päästäkseni tiedoston loppuun asti. Huomasin useita mainintoja X11 -ohjelmiston tiedostoista ja lukuisia deletoituja tiedostoja. Päättelin siitä, kun samaiset tiedostot löytyivät unallocated/ hakemistosta, että nämä ovat ne deletoidut tiedostot. Tiedostojen seassa on etsimäni rootkit.

gettimes deleted files

Varsinkin yksi rivi pisti silmään:

gettimes epailyttava tiedosto

Kyseinen .tgz -tiedosto löytyy unallocated/ -hakemistosta.

Tiedostojen deletointi alkaa 13:17:36 ja loppuu seuraavana päivänä 18:28:30, jolloin viimeinen tiedosto deletoidaan (etc/rc.d/rc3.d/K83ypbind (deleted-realloc)). Tein deleted -kohtien etsimisestä helpompaa komennolla:

$ less output/times2001-03-15.txt | grep deleted

Luin less -työkalua käyttäen myös ils_times2001-03-15.txt -tekstitiedoston. Sain täten tietää inode arvot, eli metadata osoitteet (lukevat kyllä myös times2001-03-15.txt -tiedostossa).

ils times inodes

Purin lk.tgz -tiedoston tarkistaakseni sisällön. Sisältä löysin:

lk last sisalto

Cleaner on bash skripti, sauber tarkoittaa puhdistajaa, olettaisin sen puhdistavan lokitiedostoja. Install asentaa rootkitin.

 

Tehtävän tekemisessä auttoi huomattavasti:

Sleuthkit työkalujen dokumentointi: http://wiki.sleuthkit.org/index.php?title=TSK_Tool_Overview

Linux -järjestelmän asentaminen, Apache2, PHP, SSH ja MySQL

Tarkemmat tiedot: http://terokarvinen.com/2012/linux-koe-tyoasemat-ja-tietoverkot-25

Kyseessä on siis työasemat ja tietoverkot -kurssin Linux -osion koe. Etenen aika hitaasti, koska raportoin samaan aikaan.

Käytössä vanha HP:n pöytäkone t650.fi:

Käyttöjärjestelmänä Xubuntu 12.10 32-bit.

Xubuntua asentaessa tuli jonkin verran ongelmia. Xubuntun asennusohjelma LiveCD -tilassa sulkeutui itsestään ja asennusprosessi pysähtyi siihen. Kokeilin Ubuntu 12.04 LTS:llä, mutta tuli sama ongelma vastaan. Ratkaisin ongelman polttamalla levylle Xubuntu 12.04 LTS Alternate -levykuvan.

Normaalisti Xubuntu asennettaisiin LiveCD -tilassa, jossa pääsee kokeilemaan Xubuntun työpöytää ja mukana tulevia ohjelmia. Koska asennus LiveCD -tilassa ei toimi, käyttöjärjestelmä asennetaan hyvin paljon samaan tapaan kuin Windows XP, eli käyttöliittymä ei ole erityisen kaunis.

15:07 – Aloitin Xubuntun asentamisen.

  • Kieleksi englanti
  • Suomelainen näppäimistö
  • Aikavyöhykkeenä Helsinki
  • Alustin koko #1 osion
  • Xubuntun asentamisen kokonaiskesto: 15:07 – 15:54

Kello 15:57 tietokone oli taas auki ja Xubuntu oli asennettuna.

“Asiakas” haluaa työasemalla onnistuvan internetin selaaminen ja tekstinkäsittely. Mozillan Firefox on valmiina asennettuna ja tekstinkäsittelyä varten on AbiWord. Päätin kuitenkin asentaa LibreOfficen järjestelmään. Terminaaliin siis:

 $ sudo apt-get install libreoffice

Asiakas myös pyysi Apache2 -webpalvelinta, PHP -kooditukea sitä varten, openSSH etäyhteyksiä varten. Asennetaan kyseiset ohjelmistot seuraavaksi (kello on tässä vaiheessa 16:09). Asennetaan ensimmäiseksi Apache2 ja laitetaan päälle userdir kotisivuja varten:

$ sudo apt-get install apache2

Kokeillaan tässä vaiheessa toimiiko Apache2. Sen saa selville kun Firefox -selaimeen kirjoittaa “http://localhost&#8221;. Jos sivulla lukee “It works!”, Apache2 on asennettu onnistuneesti. Laitetaan päälle userdir:

$ sudo a2enmod userdir

Terminaalissa lukeekin mikä komento pitää antaa seuraavaksi, että saa kotisivut toimimaan. Kyseinen komento on:

$ sudo service apache2 restart

Tämä käynnistää Apache2 -demonin uudelleen. Kun se on uudelleen käynnistetty, userdir on käytössä. Nyt vain puuttuu public_html -kansio ja index.html -tiedosto kyseisen kansion sisältä. Tarkista oletko kotihakemistossa komennolla

$ pwd

Jos hakemisto jossa olet on “/home/*käyttäjänimi*”, olet oikeassa paikassa. Kirjoitetaan:

$ mkdir public_html

Tehdään sinne index.html -tiedosto:

$ nano public_html/index.html

Näin avautuu tekstieditori nano. Kirjoitetaan tiedostoon väliaikaisesti “Moi.” ja tallennetaan tiedosto. Tämä tapahtuu Ctrl + x -näppäinyhdistelmällä ja painamalla sen jälkeen y:tä kun kysytään, halutaanko tallentaa muutokset. Kirjoitetaan Firefox -selaimen osoitepalkkiin “http://localhost/~*käyttäjänimi*&#8221;. Sivulla pitäisi lukea “Moi.”

Asennetaan seuraavaksi PHP5 -tuki Apache2 -webpalvelimeen. Haetaan ja asennetaan PHP5 -moduuli Apache2:sta varten:

$ sudo apt-get install php5

Navigoidaan hakemistoon /etc/apache2/mods-available komennolla:

$ cd /etc/apache2/mod-available

php5.conf -tiedostoa pitää editoida, jotta Apache2 pystyy käyttämään PHP -ohjelmointikieltä. Kirjoitetaan:

$ sudo nano php5.conf

Tiedoston sisällä on ohjeetkin tätä varten, mutta kerrotaan se tässäkin: nano editorissa, “kommentoidaan ulos” eli laitetaan # -merkit kaikkien rivien eteen alkaen <IfModule mod_userdir.c> -rivistä viimeiseen </IfModule> riviin asti (älä laita # -merkkiä vikan rivin eteen!). Painetaan Ctrl+x ja kun kysytään, halutaanko tallentaa muutokset, valitaan kyllä (y). Käynnistetään apache2 uudelleen:

$ sudo service apache2 restart

Nyt PHP -koodin pitäisi toimia. Tätä voi kokeilla kirjoittamalla vaikka index.html -tiedostoon:

<?php
print("Terve")
?>

Yllä oleva koodi tulostaa aikaisemman “Moi.” perään “Terve”.

Tiedostopääte pitää muuttaa .html:stä .php:ksi jos haluaa koodin toimivan. Jos index.html on kansiossa, kyseinen sivu avataan automaattisesti kun avaa osoitteen “##.##.##.##/~*käyttäjänimi*”, “127.0.0.1/~*käyttäjänimi*” tai “localhost/~*käyttäjänimi*”.

Klo 17:00 – asennetaan OpenSSH:

$ sudo apt-get install openssh-server

Tässä tapauksessa asennetaan openssh-server, koska haluamme etäkäyttää tietokonetta muilta koneilta.

Siirrytään käyttäjien luontiin. “Asiakkaan” yrityksessä ovat työntekijät Einari Vähäkäähkä, Pekka Winha, Åke Andersson ja Leila Laila. He haluavat kehittää PHP -sivuja etäkäyttöyhteydellä. Joten heille luodaan omat käyttäjätunnukset ja esimerkkisivut.

$ sudo adduser *käyttäjänimi*

Täytä tiedot mitä kysytään.

sudo adduser + questions

Kun kaikki käyttäjätunnukset ovat luotu, kirjaudutaan kaikilla sisään vuorotellen ja luodaan public_html -kansiot kotihakemistoihin ja niihin index.php -tiedostot. Samalla voi tarkistaa toimiiko käyttäjätilit.

Esimerkki index.php:

esimerkki-index.php

Toinen tapa millä saman voi tehdä on käyttää OpenSSH:ta. Yhdistäminen ja sisäänkirjautuminen tapahtuu komennolla:

$ ssh *käyttäjänimi*@*iposoite*

Tässä tapauksessa, jos haluamme tehdä Leila Lailalle tällä tavalla esimerkki index.php tiedoston, vastaava olisi:

$ ssh lailalei@127.0.0.1

Muuten sama prosessi uudestaan, eli tehdään kotihakemistoon kansio public_html ja sinne index.php.

ssh mkdir nano index.php

Koko prosessi kesti noin 15 minuuttia, mukaan lukien testaus.

Tässä vaiheessa on toteutettu “asiakkaan” kaksi ensimmäistä toivetta, eli tekstinkäsittely ja nettiselain sekä PHP-sivujen etäkehitys. Minulta meni noin 3 tuntia kokonaisuudessaan (mukaan lukien dokumentointi).

Seuraavaksi teemme skriptin “mystatus” mikä näyttää vapaan levytilan ja ip-osoitteen. Luodaan ensiksi itse skripti nanolla:

$ nano mystatus

Tiedostoon kirjoitetaan komennot

df -h

ja

ip addr

Tässä vaiheessa voi tallentaa ja sulkea nanon ja kokeilla skriptiä:

$ bash mystatus

Tuloksen pitäisi näyttää tältä:

bash mystatus

Avataan taas nano (nano mystatus). Kirjoitetaan ennen komentoja:

#!/bin/bash

Esimerkki:

nano mystatus

Tallennetaan ja suljetaan nano. Kirjoitetaan komento:

$ ./mystatus

Vastaukseksi tulee “bash: ./mystatus: Permission denied”, niin kuin pitääkin. mystatus -skriptin oikeudet pitää määritellä chmod -komennolla. Tässä tapauksessa annan kaikille oikeudet lukea ja ajaa skriptin:

$ chmod a=rx mystatus

Tämän jälkeen komennon “./mystatus” pitäisi toimia. Komennossa “a” viittaa kaikkiin, “r” lukuoikeuksiin ja “x” ajo-oikeuksiin.

Jotta skripti toimisi ilman “./” etuliitettä, tiedosto pitää kopioida /usr/bin/ -hakemistoon:

$ sudo cp -i mystatus /usr/bin/

-i asetus komennossa kysyy, halutaanko ylikirjoittaa, jos kohdehakemistossa on saman niminen tiedosto. Tämä estää sen, että vahingossa ylikirjoittaisi jonkin mahdollisesti tärkeän tiedoston.

Nyt “mystatus” skriptin pitäisi toimia kuin minkä tahansa komennon. Skriptin luontiin, testaamiseen ja dokumentointiin meni puoli tuntia.

“Asiakas” halusi myös lisäksi Pekka Winhalle MySQL -tietokannan sekä sen hallintaa varten phpMyAdmin -verkkokäyttöliittymän. Asennetaan ensiksi mySQL -palvelin:

$ sudo apt-get install mysql-server

Luo salasana kun sitä pyydetään.

Seuraavaksi phpMyAdmin:

$ sudo apt-get install phpmyadmin

Tässä ruudussa välilyöntiä pitää painaa, että voi pistää rastin ruutuun. Sen jälkeen enter:stä pääsee eteen päin.

phpmyadmin auto-configuration

Seuraavassa ruudussa valitaan yes:

phpmyadmin package configuration

Ja tehdään vielä salasana phpMyAdmin -käyttöliittymälle:

phpmyadmin package configuration password

Nyt voi kokeilla, onnistuiko asennus kirjoittamalla selaimeen osoitteeksi “http://*oma ip-osoite*/phpmyadmin. Sivun pitäisi näyttää tältä:

phpmyadmin webUI

 

 

Hello World -ohjelma 3 eri ohjelmointikielellä

Tässä artikkelissa käsittelen helloworld -ohjelman luomista kolmella eri ohjelmointikielillä.

C++:

Ensiksi pitää hankkia c++ -kääntäjä. Linuxilla on g++, avoimen lähdekoodin c++ -käännin. Latasin ja asensin ohjelman komennolla $ sudo apt-get install g++, jonka jälkeen loin helloworld.cc -nimisen tiedoston nanolla (nano helloworld.cc). Kirjoitin tiedostoon:

#include <iostream>
using namespace std;

int main() {

std::cout << “Hello World!” << std::endl;
return 0;

}

Ja annoin komennon $ g++ helloworld.cc kääntääkseni lähdekoodin. Tuloksena oli a.out -niminen tiedosto, jonka ajoin komennolla $ ./a.out. Ohjelma tulosti terminaaliin “Hello World!” -tekstin.

 

C#:

Asensin ensiksi mono-mcs:n komennolla $ sudo apt-get install mono-mcs. Tämän jälkeen kirjoitin tiedoston nimeltä HelloWorld.cs:

class HelloWorld {
static void Main() {
System.Console.WriteLine(“Hello World!”);
}
}

Ja annoin komennon $ mcs HelloWorld.cs. Tuloksena oli HelloWorld.exe, jonka ajoin komennolla $ mono HelloWorld.exe. Terminaaliin tulostui “Hello World!”.

 

Java:

Java -ohjelmointia varten tarvitsee Linuxilla openjdk:n, eli open java development kitin. Kirjoittaa $ sudo apt-get install openjdk ja painaa tabulaattoria, niin saa listan openjdk -paketeista. Uusin tämän artikkelin aikana oli openjdk-7-jdk, mutta latasin silti openjd-6-jdk:n. Pitää muistaa ladata openjdk-#-jdk -niminen paketti, koska muut eivät ole development kittejä. Lataamisessa voi kestää hiukan: itselläni kesti noin kaksi minuuttia tasaisella 748 kbps nopeudella.

Kun olin asentanut openjdk, aloin tekemään HelloWorld.java -lähdekoodia ($ nano HelloWorld.java). Koodi näytti tältä:

public class HelloWorld {
public static void main(String[]args) {
System.out.println(“Hello World!”);
}
}

Käänsin lähdekoodin komennolla $ javac HelloWorld.java, ja tuloksena oli HelloWorld.class, jonka join komennolla $ java HelloWorld. Ohjelma tulosti terminaaliin “Hello World!”.

 

Lähteet:

HelloWorld -ohjeet: Programming Languages on Linux – Installing and Using on Ubuntu

Lähdekoodit (c++ ja c#, javan kirjoitin itse): Transwiki:List of hello world programs