Information wants to be free...

64-Bit Number Conversion Tool

Around 10 years ago I posted about a number conversion tool. Several improvements have made it into the tool in those years, so it's time for an update. The most notable change is the support for 64-bit resolution on the numbers (also when compiled on 32-bit systems). In addition there is support for more operators like and (&), or (|), multiplication (*), division (/) and modulus (%).

Here is the updated code:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <limits.h> /* __WORDSIZE */

static uint64_t power_of_two(uint64_t n)
{
  if (n == 0)
    return 1;
  else
    return 2 * power_of_two(n - 1);
}

static char *convert_to_bin(uint64_t integer)
{
  static char buffer[65]; /* Should not be longer than 64-bits + NULL. */
  int i, first_one;

  buffer[64] = '\0'; /* Always terminate. */

  first_one = 63;
  for (i = 63; i >= 0; i--) {
    if (integer >= power_of_two(i)) {
      buffer[63 - i] = '1';
      if (first_one > (63 - i))
      {
        first_one = (63 - i);
      }
      integer -= power_of_two(i);
    } else {
      buffer[63 - i] = '0';
    }
  }

  return &buffer[first_one];
}

static uint64_t convert_from_bin(char *number)
{
  uint64_t value;
  int n;
  char *p;

  value = 0; 
  n = strlen(number) - 3; /* Also minus "0b". */
  p = &number[2];
  do {
    if (*p == '0') {
      n--;
    } else if (*p == '1') {
      value += power_of_two(n);
      n--;
    }
  } while (*p++ != '\0');

  return value;
}

static uint64_t convert_to_int(char *number)
{
  uint64_t integer;

  if (strncmp(number, "0x", 2) == 0) {
    integer = strtoull(number, NULL, 16);
  } else if (strncmp(number, "0b", 2) == 0) { 
    integer = convert_from_bin(number);
  } else if (strncmp(number, "-", 1) == 0) {
    integer = atoll(number); /* Handle signed integers. */
  } else {
    integer = strtoull(number, NULL, 10);
  }
  
  return integer;
}

static uint64_t biggest_int(uint64_t n1, uint64_t n2, uint64_t n3)
{
  /* NOTE: Does not handle signed integers, so padding will be off. */
  if (n1 > n2 && n1 > n3) {
    return n1;
  } else if (n2 > n3) {
    return n2;
  } else {
    return n3;
  }
}

static void display_int(uint64_t integer, char *prefix, uint64_t biggest)
{
  int pad;

  if (prefix != NULL)
  {
    printf("%s", prefix);
  }

#if (__WORDSIZE == 64)
  pad = snprintf(NULL, 0, "%ld", biggest)
      - snprintf(NULL, 0, "%ld", integer);
#else /* __WORDSIZE == 32 */
  pad = snprintf(NULL, 0, "%lld", biggest)
      - snprintf(NULL, 0, "%lld", integer);
#endif /* __WORDSIZE */
  while (pad-- > 0) {
    printf(" ");
  }

#if (__WORDSIZE == 64)
  printf("%ld ", integer);
#else /* __WORDSIZE == 32 */
  printf("%lld ", integer);
#endif /* __WORDSIZE */

  printf("0x");
#if (__WORDSIZE == 64)
  pad = snprintf(NULL, 0, "%lx", biggest)
      - snprintf(NULL, 0, "%lx", integer);
#else /* __WORDSIZE == 32 */
  pad = snprintf(NULL, 0, "%llx", biggest)
      - snprintf(NULL, 0, "%llx", integer);
#endif /* __WORDSIZE */
  while (pad-- > 0) {
    printf("0");
  }

#if (__WORDSIZE == 64)
  printf("%lx ", integer);
#else /* __WORDSIZE == 32 */
  printf("%llx ", integer);
#endif /* __WORDSIZE */

  printf("0b");
  pad = strlen(convert_to_bin(biggest))
      - strlen(convert_to_bin(integer));
  while (pad-- > 0) {
    printf("0");
  }

  printf("%s\n", convert_to_bin(integer));
}

int main(int argc, char *argv[])
{
  uint64_t n1, n2, result, biggest;
  char *prefix;

  if (argc == 2) { /* Just display the number in different forms. */
    n1 = convert_to_int(argv[1]);
    display_int(n1, NULL, n1);

  } else if (argc == 4) { /* Perform extra operation. */
    n1 = convert_to_int(argv[1]);
    n2 = convert_to_int(argv[3]);
    if (argv[2][0] == '+') {
      result = n1 + n2;
      prefix = " + ";
    } else if (argv[2][0] == '-') {
      result = n1 - n2;
      prefix = " - ";
    } else if (argv[2][0] == '^') {
      result = n1 ^ n2;
      prefix = " ^ ";
    } else if (argv[2][0] == '&') {
      result = n1 & n2;
      prefix = " & ";
    } else if (argv[2][0] == '|') {
      result = n1 | n2;
      prefix = " | ";
    } else if (argv[2][0] == '*') {
      result = n1 * n2;
      prefix = " * ";
    } else if (argv[2][0] == '/') {
      result = n1 / n2;
      prefix = " / ";
    } else if (argv[2][0] == '%') {
      result = n1 % n2;
      prefix = " % ";
    } else {
      fprintf(stderr, "%s: error: invalid operator.\n", argv[0]);
      return -1;
    }
    biggest = biggest_int(n1, n2, result);
    display_int(n1, "   ", biggest);
    display_int(n2, prefix, biggest);
    display_int(result, " = ", biggest);

  } else {
    fprintf(stderr, "Usage: %s <number> [<operator> <number>]\n", argv[0]);
    fprintf(stderr, "  number can be integer decimal, hex (0x) or binary (0b)\n");
    fprintf(stderr, "  valid operators: + - ^ & | * / %%\n");
    return -1;
  }
   
  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 09/03-2019, Article Link

Battery Leakage Report

I have several old retro PCs lying around. And as explained in detail here, battery leakage is a risk to some of these PCs depending on the type of battery. So I have gone through a total of 13 PCs and removed leaking NiCd and NiMH batteries from 4 of them, which had those. I have compiled a complete report of all the PCs with image collages here.

Information on the following branded PCs can be found in the report (in addition to 4 custom builds):
* Commodore PC 20-III
* Commodore PC 30-III
* IBM PS/ValuePoint 433DX/Si
* Zenith ICL-4434-KT
* NCR System 3330
* Compaq Deskpro 386s/20
* Compaq ProLinea 590
* Brother BCN3386SX/52
* Laser 486DX2-50E

Here is one of the worst cases of damage:

NiCd battery leak


Topic: Mundane, by Kjetil @ 09/02-2019, Article Link

uIP support for GR-SAKURA

I have now managed to get the uIP TCP/IP network stack running on the GR-SAKURA board. I'm using the Renesas PHY driver, but my own heavily modified (and simplified) implementation of the Ethernet driver.

The Renesas Ethernet controller has a "Zero Copy" mechanism that uses DMA transfer on incoming and outgoing packets. This is nice and all, but it comes into conflict with the uIP architecture with a single buffer. So I ended up with the separate single uIP buffer and two separate ethernet buffers instead, doing CPU based (memcpy) copying between them. Even though this is not as efficient, it prevents the need for any modifications to the core uIP source code.

One limitation with this initial implementation is that there is no handling of link up or down. The code expects the Ethernet plug to be plugged in at boot and remain connected.

To start using it, get the code from here and unpack it in the uIP source folder. (The "sakura" directory should be alongside the "unix" directory.) Then run "make" inside the "sakura" directory to create the "uip.bin" binary that may be uploaded the GR-SAKURA board.

I have also forked the original uIP repository and made the necessary changes on GitHub.

Topic: Open Source, by Kjetil @ 01/01-2019, Article Link

Joystick Mouse on GR-SAKURA

Here is another project based on the GR-SAKURA board. It translates joystick movement into mouse movement, so it can be used on a PC. This is done reading three analog inputs from the joystick and forwarding data over an UART using the simple Microsoft Serial Mouse format.

The software is based heavily on the 30JH joystick that I happen to have. This is a 3-axis joystick, and the last axis is used to simulate the button presses.

This command in Linux can be used to get it connected:

sudo inputattach -bare /dev/ttyUSB0
          


Here's a photo of the setup:

GR-SAKURA wth Joystick


Here is the main part of the code, which implements the serial mouse protocol:

#include "uart.h"
#include "adc.h"

/* Raw ADC reference values: */
#define JOYSTICK_POS_MAX 2464
#define JOYSTICK_POS_MIN 2080 /* JOYSTICK_POS_MAX - (128 * 3) */
#define JOYSTICK_CENTER  2048
#define JOYSTICK_NEG_MIN 2016 /* JOYSTICK_NEG_MAX + (128 * 3) */
#define JOYSTICK_NEG_MAX 1632

static unsigned char prev_buttons = 0;

static unsigned char convert_adc_to_move_value(short value)
{
  /* Scaling and manual conversion to two's complement. */
  if (value > JOYSTICK_POS_MIN) {
    if (value > JOYSTICK_POS_MAX) {
      value = JOYSTICK_POS_MAX;
    }
    return (value - (JOYSTICK_POS_MIN + 1)) / 3;

  } else if (value < JOYSTICK_NEG_MIN) {
    if (value < JOYSTICK_NEG_MAX) {
      value = JOYSTICK_NEG_MAX;
    }
    return ((value - (JOYSTICK_NEG_MAX + 1)) / 3) + 128;

  } else {
    return 0; /* Filtered. */
  }
}

static unsigned char convert_adc_to_button_value(short value)
{
  if (value > JOYSTICK_POS_MIN) {
    return 0b10; /* Left button pressed. */

  } else if (value < JOYSTICK_NEG_MIN) {
    return 0b01; /* Right button pressed. */

  } else {
    return 0b00; /* No buttons pressed. */
  }
}

static short invert_adc_value(short value)
{
  if (value > JOYSTICK_CENTER) {
    return JOYSTICK_CENTER - (value - JOYSTICK_CENTER);
  } else {
    return JOYSTICK_CENTER + (JOYSTICK_CENTER - value);
  } 
}

static int mouse_format(short x_move_adc, short y_move_adc,
                        short button_adc, unsigned char *data)
{
  unsigned char x_move, y_move, buttons;

  x_move = convert_adc_to_move_value(x_move_adc);
  y_move = convert_adc_to_move_value(invert_adc_value(y_move_adc));
  buttons = convert_adc_to_button_value(button_adc);

  data[0] = 0b11000000;
  data[1] = 0b10000000;
  data[2] = 0b10000000;

  data[0] |= (buttons << 4);
  data[0] |= ((y_move >> 4) & 0b00001100);
  data[0] |= ((x_move >> 6) & 0b00000011);
  data[1] |= (x_move & 0b00111111);
  data[2] |= (y_move & 0b00111111);

  if (y_move > 0 || x_move > 0 || buttons != prev_buttons) {
    prev_buttons = buttons;
    return 1; /* Activity */
  } else {
    return 0; /* No activity */
  }
}

void mouse_loop(void)
{
  unsigned char data[3];

  while(1) {
    asm("wait");
    if (mouse_format(adc_get(0), adc_get(1), adc_get(2), data)) {
      uart0_send(data, 3);
    }
  }
}
          


Get the complete code under the MIT License here, which requires the RX GCC toolchain to compile.

Topic: Open Source, by Kjetil @ 12/12-2018, Article Link

eLua support for GR-SAKURA

I have managed to get eLua running on the GR-SAKURA board. It's an embedded version of version of The Programming Language Lua that can be run on bare-metal.

I forked the original eLua repository and made the necessary changes here on my own copy. Note however that this initial support is very basic; only limited versions of the "tmr", "uart" and "pio" modules are supported at the moment.

Here is an example Lua script that can be run on the board. Name it "autorun.lua" and put it in the "romfs/" directory to run it automatically on startup:

require "pio"
require "pd"
require "tmr"

print("Running LEDs on " .. pd.board())
print("Hold blue button to stop.")

while pio.pin.getval(7) == 1 do
  pio.pin.sethigh(0)
  tmr.delay(0,200000)
  pio.pin.sethigh(1)
  tmr.delay(0,200000)
  pio.pin.sethigh(2)
  tmr.delay(0,200000)
  pio.pin.sethigh(6)
  tmr.delay(0,200000)

  pio.pin.setlow(0)
  tmr.delay(0,200000)
  pio.pin.setlow(1)
  tmr.delay(0,200000)
  pio.pin.setlow(2)
  tmr.delay(0,200000)
  pio.pin.setlow(6)
  tmr.delay(0,200000)
end

print("Finished.")
          


Topic: Open Source, by Kjetil @ 09/11-2018, Article Link

GR-SAKURA Shell Update

I have added two new commands to the GR-SAKURA Shell. One is just a "button" command to print the state of the blue push button, known as SW2. The other "adc" command prints the binary value of the 12-bit A/D converter connected to analog inputs AN0 to AN5.

ADC command:

sakura> adc
AN0: 101111011010
AN1: 110000101110
AN2: 110001111111
AN3: 100000001000
AN4: 000000000001
AN5: 110000110001
sakura> adc
AN0: 101110111011
AN1: 110000000010
AN2: 110001010111
AN3: 110001000001
AN4: 011111000010
AN5: 000000000011
          


Button command:

sakura> button
released
sakura> button
pressed
          


Get the source code here or from my GitLab or GitHub pages.

Also consider using my updated instructions on to build the cross compilation toolchain for the Renesas RX CPU.

Topic: Open Source, by Kjetil @ 11/10-2018, Article Link

Reverse SSH Tunnel Launcher

Here is a script which helps with starting a reverse SSH tunnel. It gets the instructions from a web server, in the form of a JSON file, which determines at what time to open the tunnel(s). The JSON file also determines when to check for a JSON file next time. It operates on a daily schedule and uses the HH:MM format.

The idea is to leave this script running on a box behind a firewall. It is hardcoded to only keep the tunnel open for 5 minutes before closing it again. So to get a connection that lasts longer, a new tunnel probably has to be made manually after connecting.

The Python code:

#!/usr/bin/python

import urllib2
import json
import subprocess
import signal
import datetime
import time
import logging
import sys

config_url = "http://192.168.0.1/config.json"
ssh_host = "192.168.0.1"
ssh_port = 22
local_port = 1337
tunnel_open_time = 300 # In seconds.

tunnel = None

def alarm_handler(signum, frame):
    global tunnel
    logger.info("Terminating current tunnel.")
    tunnel.kill();
    tunnel = None

def minutes_until(minute_ts):
    if minute_ts == None:
        return (24 * 60)

    now = datetime.datetime.now().timetuple()
    now_ts = (now.tm_hour * 60) + now.tm_min

    if now_ts > minute_ts:
        # Already passed, will be next day.
        return minute_ts - now_ts + (24 * 60)
    else:
        return minute_ts - now_ts

def update_config():
    try:
        config = json.load(urllib2.urlopen(config_url))

        # Pick closest time to update next time:
        closest_config_ts = None
        for uc in config["ConfigUpdate"]:
            config_ts = (int(uc.split(":")[0]) * 60) + int(uc.split(":")[1])
            if minutes_until(config_ts) < minutes_until(closest_config_ts):
                closest_config_ts = config_ts

        tunnel_ts = list()
        for to in config["TunnelOpen"]:
            tunnel_ts.append((int(to.split(":")[0]) * 60) + int(to.split(":")[1]))

        logger.debug("Config timestamp: %d" % (closest_config_ts))
        logger.debug("Tunnel timestamps: %s" % (str(tunnel_ts)))

        return closest_config_ts, tunnel_ts

    except Exception, e:
        logger.error("Exception during config update: %s" % (e))
        return 0, []

if __name__ == "__main__":
    logger = logging.getLogger('reverse_tunnel')
    logger.setLevel(logging.DEBUG)
    logger.addHandler(logging.StreamHandler())

    # Get initial configuration:
    config_ts, tunnel_ts = update_config()
    if len(tunnel_ts) == 0:
        sys.exit(1) # Exit right away if initial configuration fails.

    signal.signal(signal.SIGALRM, alarm_handler)

    while True:
        now = datetime.datetime.now().timetuple()
        now_ts = (now.tm_hour * 60) + now.tm_min

        # Is it time for a config update?
        if now_ts == config_ts:
            logger.info("Timed config update.")
            config_ts, tunnel_ts = update_config()
            if config_ts == now_ts:
                config_ts = config_ts - 2 # Prevent immediate update.

        # Is it time to open a new tunnel?
        for ts in tunnel_ts:
            if now_ts == ts:
                if tunnel == None:
                    logger.info("Opening new tunnel.")
                    tunnel = subprocess.Popen(["ssh", "-R", str(local_port) + ":localhost:22", "-N", "-p", str(ssh_port), ssh_host])
                    signal.alarm(tunnel_open_time)
                else:
                    logger.warning("Tunnel already running.")

        time.sleep(10)
          


Example JSON file:

{
  "ConfigUpdate" : ["17:00"],
  "TunnelOpen" : ["18:00","19:00","20:00"]
}
          


Topic: Scripts and Code, by Kjetil @ 29/09-2018, Article Link

DOS Serial File Transfer

I was doing research on how to "bootstrap" an old DOS based computer with more functional software, like a proper terminal emulator. Because the floppy drive was broken, I had to get this transferred over the existing RS-232 serial port.

The initial results were a couple of programs: A "decode" program meant to be run on DOS, written in x86 assembly, to receive the data. An "encode" program meant to be run on Linux, written in C, to send the data. Very simple encoding is used; each byte is split into two nibbles and added with 0x30 to produce a valid ASCII character.

Unfortunately I was not able to run the programs with larger amounts of data without loosing bits and pieces. Even at very low baud rates like 1200 data still got lost and enabling/disabling flow control made no difference. However, I found that other people have been suffering from these kinds of issues in the past as well. So I found some other alternatives, and the best one so far is Kermit. The MS-DOS Kermit package also provides a BASIC bootstrap program that can be run in QBASIC on the DOS computer.

Anyway, for future reference, here is the "decode" program:
(Assemble it under Linux with NASM like so: nasm decode.asm -fbin -o decode.com)

org 0x100 ; DOS COM file start offset.

section .text
start:
  ; Call BIOS to initialize serial port.
  mov ah, 0x00
  mov al, 0xe3 ; 9600,8,n,1
  mov dx, 0x00 ; COM1
  int 0x14

  ; Call DOS to create new file and handle.
  mov ah, 0x3c
  mov cx, 0 ; Standard attributes.
  mov dx, filename
  int 0x21
  jc end_file_create_error
  mov [filehandle], ax

read_loop:
  ; Call BIOS to read byte from serial port.
  mov ah, 0x02
  mov dx, 0x00 ; COM1
  int 0x14
  and ah, 0x80
  jnz end_read_serial_error

  ; Check if DOS-style EOF.
  cmp al, 0x1A
  je end_success

  ; Determine high or low nibble next.
  mov bl, [have_seen_high_nibble]
  cmp bl, 0
  jne convert_low_nibble

convert_high_nibble:
  sub al, 0x30
  mov cl, 4
  shl al, cl
  mov [high_nibble], al
  mov byte [have_seen_high_nibble], 1
  jmp read_loop

convert_low_nibble:
  sub al, 0x30
  or al, [high_nibble]
  mov byte [have_seen_high_nibble], 0

  ; Call DOS to write to file.
  mov [write_buffer], al
  mov ah, 0x40
  mov bx, [filehandle]
  mov cx, 1 ; One byte at a time.
  mov dx, write_buffer
  int 0x21
  jc end_file_write_error

  ; Call DOS to display '.' for progress.
  mov ah, 0x2
  mov dl, '.'
  int 0x21

  jmp read_loop

end_success:
  ; Call DOS to close file handle.
  mov ah, 0x3e
  mov bx, [filehandle]
  int 0x21

  ; Call DOS to terminate program.
  mov ah, 0x4c
  mov al, 0 ; 0 = OK
  int 0x21

end_file_create_error:
  mov [error_code], ax
  ; Call DOS to display 'C' signifying "Create Error".
  mov ah, 0x2
  mov dl, 'C'
  int 0x21

  ; Call DOS to display error code.
  ; mov ah, 0x2
  mov dl, [error_code]
  add dl, 0x30
  int 0x21
  jmp end_error

end_read_serial_error:
  ; Call DOS to display 'R' signifying "Read Error".
  mov ah, 0x2
  mov dl, 'R'
  int 0x21

  ; Call BIOS to get serial port status
  mov ah, 03
  mov dx, 0x00 ; COM1
  int 0x14
  mov [error_code], ax

  ; Call DOS to display error code(s).
  mov ah, 0x2
  mov dl, [error_code] ; Modem Status
  mov cl, 4
  shr dl, cl ; High Nibble
  add dl, 0x30
  int 0x21

  ; mov ah, 0x2
  mov dl, [error_code] ; Modem Status
  and dl, 0x0f ; Low Nibble
  add dl, 0x30
  int 0x21

  ; mov ah, 0x2
  mov dl, [error_code+1] ; Port Status
  mov cl, 4
  shr dl, cl ; High Nibble
  add dl, 0x30
  int 0x21

  ; mov ah, 0x2
  mov dl, [error_code+1] ; Port Status
  and dl, 0x0f ; Low Nibble
  add dl, 0x30
  int 0x21
  jmp end_error

end_file_write_error:
  mov [error_code], ax
  ; Call DOS to display 'W' signifying "Write Error".
  mov ah, 0x2
  mov dl, 'W'
  int 0x21

  ; Call DOS to display error code.
  ; mov ah, 0x2
  mov dl, [error_code]
  add dl, 0x30
  int 0x21

end_error:
  ; Call DOS to terminate program.
  mov ah, 0x4c
  mov al, 1 ; 1 = Error
  int 0x21
 
section .data:

high_nibble:
  db 0

have_seen_high_nibble:
  db 0

error_code:
filehandle:
  dw 0

write_buffer:
  db 0

filename:
  db "DECODE.BIN",0
          


And the "encode" program:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <sys/ioctl.h>

int main(int argc, char *argv[])
{
  int c, fd, result;
  unsigned char byte;
  struct termios attr;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s <TTY>\n", argv[0]);
    return 1;
  }

  fd = open(argv[1], O_RDWR | O_NOCTTY);
  if (fd == -1) {
    fprintf(stderr, "open() failed with errno: %d\n", errno);
    return 1;
  }

  result = tcgetattr(fd, &attr);
  if (result == -1) {
    fprintf(stderr, "tcgetattr() failed with errno: %d\n", errno);
    close(fd);
    return 1;
  }

  attr.c_cflag = B9600 | CS8 | CRTSCTS | CLOCAL;
  attr.c_iflag = 0;
  attr.c_oflag = 0;
  attr.c_lflag = 0;

  result = tcsetattr(fd, TCSANOW, &attr);
  if (result == -1) {
    fprintf(stderr, "tcgetattr() failed with errno: %d\n", errno);
    close(fd);
    return 1;
  }

  while ((c = fgetc(stdin)) != EOF) {
    byte = ((c & 0xf0) >> 4) + 0x30;
    write(fd, &byte, 1);
    byte = (c & 0x0f) + 0x30;
    write(fd, &byte, 1);
  }

  byte = 0x1a; /* DOS EOF */
  write(fd, &byte, 1);

  close(fd);
  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 19/08-2018, Article Link

File Batch Splitter

This is a Python script that takes a directory with a lot of files and splits the files among subfolder batches of a certain size. I've hard-coded the size of a standard CD (~700MB) into the script since this is a very typical use case.

Have a look:

#!/usr/bin/python

import os

class Batch(object):
    def __init__(self):
        self._size = 0
        self._files = list()

    def size_get(self):
        return self._size

    def files_get(self):
        return self._files

    def add(self, path):
        self._files.append(path)
        self._size += os.stat(path).st_size

class BatchManager(object):
    def __init__(self, max_size):
        self._max_size = max_size
        self._batches = list()

    def create(self, directory):
        batch = Batch()
        for filename in sorted(os.listdir(directory)):
            path = os.path.join(directory, filename)
            if os.path.isfile(path):
                if (batch.size_get() + os.stat(path).st_size) > self._max_size:
                    self._batches.append(batch)
                    batch = Batch()
                batch.add(path)
        self._batches.append(batch)

    def split(self, directory):
        for batch_no, batch in enumerate(self._batches):
            print "\nBatch #%03d, Size: %d" % (batch_no + 1, batch.size_get())
            for file_no, src_path in enumerate(batch.files_get()):
                print "%03d:%03d: %s" % (batch_no + 1, file_no + 1, src_path)
                batch_dir = "%03d" % (batch_no + 1)
                dst_dir = os.path.join(os.path.dirname(src_path), batch_dir)
                dst_path = os.path.join(os.path.dirname(src_path), batch_dir, os.path.basename(src_path))
                if not os.path.isdir(dst_dir):
                    os.mkdir(dst_dir)
                os.rename(src_path, dst_path)

if __name__ == "__main__":
    import sys
    if len(sys.argv) < 2:
        print "Usage: %s <directory>" % (sys.argv[0])
        sys.exit(1)

    directory = sys.argv[1]

    bm = BatchManager(737280000) # Bytes on 80 min CD-ROM Mode 1
    bm.create(directory)
    bm.split(directory)

    sys.exit(0)
          


Topic: Scripts and Code, by Kjetil @ 16/07-2018, Article Link

VPN Through SSH Tunnel

Not just VPN through an SSH tunnel, but also a Wi-Fi hotspot that directs all traffic through it! I needed this to be able to reach the Google Play store on my Android phone in China. I used two WLAN interfaces for this, but it should be possible with one WLAN and one wired connection as well.

VPN through SSH tunnel principle diagram.


Here are the complete steps, with the contents of the files listed after. Many of the commands are started through screen sessions, since they are daemons that will keep running.

First of all, make sure the interface that will be used to connect to the remote SSH server is up and running, then connect to the server and create the SSH tunnel for port 1194, which is used for VPN:

ssh user@your-ssh-server.com -L 127.0.0.1:1194:127.0.0.1:1194
          


One the remote server, enter the following commands:

# Start OpenVPN server:
screen -S openvpn -d -m sudo openvpn server.cfg

# Forward all the traffic from the OpenVPN tunnel interface:
sudo iptables -A INPUT -i tun0 -j ACCEPT
sudo iptables -A FORWARD -i tun0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -d 10.8.0.0/255.255.255.0 -j ACCEPT

# Enable IP forwarding on server:
sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
          


Back on the local machine, enter the following commands:

# IP address for Wi-Fi hotspot:
sudo ifconfig wlan0 192.168.8.1 netmask 255.255.255.0

# Start DHCP server for Wi-Fi hotspot:
screen -S dnsmasq -d -m sudo dnsmasq --conf-file=dnsmasq.conf --no-daemon

# Start Host-AP deamon:
screen -S hostapd -d -m sudo hostapd hostapd.conf

# Start OpenVPN client:
screen -S openvpn -d -m sudo openvpn client.cfg

# Let the SSH connection bypass the VPN default route:
SSH_IF='wlan1'
SSH_IP=`host your-ssh-server.com | sed -e 's/.*address //'`
GW_IP=`route -n | grep "^0.0.0.0" | grep $SSH_IF | sed -e 's/0.0.0.0 *//' | sed -e 's/ .*//'`
sudo route add $SSH_IP gw $GW_IP $SSH_IF

# Override the DNS resolver:
sudo cp resolv.conf /etc/resolv.conf

# Enable IP forwarding on local machine:
sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"

# Enable NATing through the tunnel interface from the Wi-Fi hotspot:
sudo iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
sudo iptables -A FORWARD -i tun0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i wlan0 -o tun0 -j ACCEPT
          


That should be it, now the phone can connect to the local Wi-Fi hotspot and reach the Internet through the remote SSH server.

Some useful commands for troubleshooting:

sudo iptables -L -v -n
sudo tcpdump -i tun0
          


OpenVPN client configuration. (client.cfg):

remote 127.0.0.1
proto tcp-client
dev tun
ifconfig 10.8.0.2 10.8.0.1
secret static.key
redirect-gateway local def1
route 192.168.0.0 255.255.255.0
          


OpenVPN server configuration. (server.cfg):

dev tun
local 127.0.0.1
proto tcp-server
ifconfig 10.8.0.1 10.8.0.2
secret static.key
          


New DNS resolver configuration, using the Google DNS. This is important because it could have been set by a local DHCP client to a Chinese DNS resolver. (resolv.conf):

nameserver 8.8.8.8
          


DHCP server configuration. (dnsmasq.conf):

interface=lo,wlan0
no-dhcp-interface=lo
dhcp-range=192.168.8.20,192.168.8.254,255.255.255.0,12h
          


Host-AP Wi-Fi hotspot configuration. These are the more important options, I have excluded lots of others. (hostapd.conf):

interface=wlan0
driver=nl80211
ssid=YourSSID
hw_mode=g
wpa=2
wpa_passphrase=YourPassphrase
wpa_key_mgmt=WPA-PSK WPA-EAP
          


Topic: Configuration, by Kjetil @ 10/06-2018, Article Link

Older articles

Newer articles