First working version.

This commit is contained in:
2023-01-13 15:52:42 +01:00
commit ebbaceaeea
10 changed files with 1150 additions and 0 deletions

115
python-ax25/README.md Normal file
View File

@@ -0,0 +1,115 @@
# python-ax25
Python AX.25 Module for Python3
## Introduction
This is a python module designed for Python3 to access AX.25 features. This module is a C extension that can access the AX.25 interface from the Linux kernel.
This C extension is inspired from pyax25 https://github.com/ha5di/pyax25
## Installing the Module
Clone the Git repository
```
$ git clone https://github.com/josefmtd/python-ax25
```
Install the module by running the install script inside the python-ax25 directory
```
$ cd python-ax25
# ./install.sh
```
## Module Functions
Before using any of the functions, make sure to load all the available ports using `config_load_ports()`
```
pythonax25.config_load_ports()
Returns = number of available ports (int)
```
To get the names of available ports, use the `config_get_first_port` and `config_get_next_port`
```
pythonax25.config_get_first_port()
Returns = name of first port (unicode string)
pythonax25.config_get_next_port(portname)
Returns = name of port after 'portname' (unicode string)
```
To retrieve further information for each available port, use these functions:
1. `config_get_port_name(device)`
2. `config_get_address(portname)`
3. `config_get_device(portname)`
4. `config_get_window(portname)`
5. `config_get_packet_length(portname)`
6. `config_get_baudrate(portname)`
7. `config_get_description(portname)`
To change the callsign from ASCII to network format and vice versa, use the functions `ascii_to_network(callsignascii)` and `network_to_ascii(callsignnetwork)`
```
pythonax25.ascii_to_network(callsignascii)
Returns = callsign in network format (byte literal string)
pythonax25.network_to_ascii(callsignnetwork)
Returns = callsign in ascii format (unicode string)
```
For receiving AX.25 packets, the packet socket is mostly used in C programs. Start a socket by using `packet_socket()` and begin receiving by using `packet_rx(fd, timeout)`
```
pythonax25.packet_socket()
Returns = file descriptor (int)
pythonax25.packet_rx(fd, timeout)
Returns = Protocol and Address (tuple of int and string) and packet (byte-literal string)
```
For sending APRS messages, the datagram socket is used. Socket is started by using `datagram_socket()`, bound to a port by using `datagram_bind(fd, srccall, portcall)` and send packets via `datagram_tx(fd, destcall, message)` or `datagram_tx(fd, destcall, digicall, message)`
```
pythonax25.datagram_socket()
Returns = file descriptor (int)
pythonax25.datagram_bind(fd, srccall, destcall)
Returns = result of bind (int)
pythonax25.datagram_tx(fd, destcall, message)
Returns = result of transmission (int)
pythonax25.datagram_tx_digi(fd, destcall, digicall, message)
Returns = result of transmission (int)
```
Closing socket is done by using `close_socket(fd)`
```
pythonax25.close_socket(fd)
Returns = result of close (int)
```
2020 - Josef Matondang

View File

@@ -0,0 +1,65 @@
#!/usr/bin/python3
import pythonax25
def parsePacket(string):
# Split the address and payload separated by APRS PID
buffer = string.split(b'\x03\xf0')
address = buffer[0]
# Check if the first byte indicates it is a data packet
if address[0] is 0:
# Cut the first byte and feed it to the address parser
listAddress = getAllAddress(address[1:])
# Get the source, destination, and digipeaters from the address list
source = listAddress[1]
destination = listAddress[0]
digipeaters = listAddress[2:]
else:
raise Exception('Not a data packet')
payload = buffer[1]
return (source, destination, digipeaters, payload)
def getAllAddress(packetAddress):
addressSize = 7
# Check if the networked address string is valid
if (len(packetAddress) % 7) is 0:
# Create a list of all address in ASCII form
allAddress = [pythonax25.network_to_ascii(packetAddress[i:i+addressSize])
for i in range(0, len(packetAddress), addressSize)]
return allAddress
else:
raise Exception('Error: Address is not a multiple of 7')
def main():
# Check if there's any active AX25 port
if pythonax25.config_load_ports() > 0:
# Get the device name of the first port
axport = pythonax25.config_get_first_port()
axdevice = pythonax25.config_get_device(axport)
axaddress = pythonax25.config_get_address(axport)
else:
exit(0)
# Initiate a PF_PACKET socket
socket = pythonax25.packet_socket()
while True:
# Blocking receive packet, 10 ms timeout
receive = pythonax25.packet_rx(socket,10)
if receive[0][1] == axdevice:
print(receive)
source, destination, digipeaters, payload = parsePacket(receive[1])
print("Packet Received by = %s"%axaddress)
print("Source Address = %s"%source)
print("Destination Address = %s"%destination)
print("Digipeaters =")
print(digipeaters)
print("Payload = %s"%payload)
print("")
else:
continue
main()

View File

@@ -0,0 +1,49 @@
#!/usr/bin/python3
import pythonax25
import time
def main():
# Check if there's any active AX25 port
if pythonax25.config_load_ports() > 0:
# Get the device name of the first port
axport = pythonax25.config_get_first_port()
axdevice = pythonax25.config_get_device(axport)
axaddress = pythonax25.config_get_address(axport)
else:
exit(0)
# Initiate a datagram socket
socket = pythonax25.datagram_socket()
srcCall = 'YD0ABH-13'
portCall = axaddress
res = pythonax25.datagram_bind(socket, srcCall, portCall)
print(res)
dest = 'APZINA'
digi = 'WIDE2-2'
msg = '!0611.08S/10649.35E$ INARad LoRa APRS#CO2=500'
res = pythonax25.datagram_tx_digi(socket, dest, digi, msg)
print(res)
time.sleep(1)
msg = 'T#001,034,034,034,034,000,11111111'
res = pythonax25.datagram_tx_digi(socket, dest, digi, msg)
print(res)
time.sleep(1)
msg = '_07190749c045s055g055t076r001h45b10101'
res = pythonax25.datagram_tx_digi(socket, dest, digi, msg)
print(res)
pythonax25.close_socket(socket)
return res
if __name__ == '__main__':
main()

17
python-ax25/install.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/bash
DIR=`dirname $0`
# Update the system
#/usr/bin/apt update
#/usr/bin/apt -y upgrade
# Install the dependencies
#/usr/bin/apt -y install libax25 libax25-dev ax25-apps ax25-tools python3-dev
# Install the Python module
${DIR}/setup.py build
${DIR}/setup.py install
# Remove the build
/bin/rm -rf "${DIR}/build"

View File

@@ -0,0 +1,352 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <poll.h>
// #include <curses.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <netax25/ax25.h>
#include <netax25/axconfig.h>
#include <netax25/axlib.h>
static PyObject * config_load_ports(PyObject* self, PyObject* args) {
int activePort;
activePort = ax25_config_load_ports();
return PyLong_FromLong(activePort);
}
static PyObject * config_get_first_port(PyObject* self, PyObject* args) {
char *portName;
portName = ax25_config_get_next(NULL);
return Py_BuildValue("s", portName);
}
static PyObject * config_get_next_port(PyObject* self, PyObject* args) {
char *portName;
char *nextPort;
PyArg_ParseTuple(args, "s", &portName);
nextPort = ax25_config_get_next(portName);
return Py_BuildValue("s", nextPort);
}
static PyObject * config_get_port_name(PyObject* self, PyObject* args) {
char *device;
char *portName;
PyArg_ParseTuple(args, "s", &device);
portName = ax25_config_get_name(device);
return Py_BuildValue("s", portName);
}
static PyObject * config_get_address(PyObject* self, PyObject* args) {
char *portName;
char *address;
PyArg_ParseTuple(args, "s", &portName);
address = ax25_config_get_addr(portName);
return Py_BuildValue("s", address);
}
static PyObject * config_get_device(PyObject* self, PyObject* args) {
char *device;
char *portName;
PyArg_ParseTuple(args, "s", &portName);
device = ax25_config_get_dev(portName);
return Py_BuildValue("s", device);
}
static PyObject * config_get_window(PyObject* self, PyObject* args) {
int window;
char *portName;
PyArg_ParseTuple(args, "s", &portName);
window = ax25_config_get_window(portName);
return PyLong_FromLong(window);
}
static PyObject * config_get_packet_length(PyObject* self, PyObject* args) {
int packetLength;
char *portName;
PyArg_ParseTuple(args, "s", &portName);
packetLength = ax25_config_get_paclen(portName);
return PyLong_FromLong(packetLength);
}
static PyObject * config_get_baudrate(PyObject* self, PyObject* args) {
int baudRate;
char *portName;
PyArg_ParseTuple(args, "s", &portName);
baudRate = ax25_config_get_baud(portName);
return Py_BuildValue("i", baudRate);
}
static PyObject * config_get_description(PyObject* self, PyObject* args) {
char *description;
char *portName;
PyArg_ParseTuple(args, "s", &portName);
description = ax25_config_get_desc(portName);
return Py_BuildValue("s", description);
}
static PyObject * aton_entry(PyObject* self, PyObject* args) {
char *callsignNetwork = null_ax25_address.ax25_call;
char *callsignString;
int result;
PyArg_ParseTuple(args, "s", &callsignString);
result = ax25_aton_entry(callsignString, callsignNetwork);
return Py_BuildValue("iy", result, callsignNetwork);
}
static PyObject * ntoa(PyObject* self, PyObject* args) {
static PyObject * callsignPython;
char *callsignNetwork;
char *callsignString;
ax25_address *callsign = &null_ax25_address;
if (!PyArg_ParseTuple(args, "y", &callsignNetwork))
fprintf(stderr, "ERROR: CANNOT ASSIGN\n");
strncpy(callsign->ax25_call, callsignNetwork, 7);
callsignString = ax25_ntoa(callsign);
callsignPython = Py_BuildValue("s", callsignString);
return callsignPython;
}
static PyObject * datagram_socket(PyObject* self, PyObject* args) {
int fileDescriptor;
fileDescriptor = socket(AF_AX25, SOCK_DGRAM, 0);
return PyLong_FromLong(fileDescriptor);
}
static PyObject * datagram_bind(PyObject* self, PyObject* args) {
struct full_sockaddr_ax25 src;
char *portcall, *srccall;
int len, sock, result;
PyArg_ParseTuple(args, "iss", &sock, &srccall, &portcall);
char * addr = malloc(sizeof(char*) * (strlen(srccall) + strlen(portcall) + 2));
sprintf(addr, "%s %s", srccall, portcall);
len = ax25_aton(addr, &src);
free(addr);
// Binding the socket to source
if (bind(sock, (struct sockaddr *)&src, len) == -1) {
result = 1;
}
else {
result = 0;
}
return PyLong_FromLong(result);
}
static PyObject * datagram_tx_digi(PyObject* self, PyObject* args) {
struct full_sockaddr_ax25 dest;
char *destcall = NULL, *digicall = NULL;
char *message;
int dlen, sock, result;
PyArg_ParseTuple(args, "isss", &sock, &destcall, &digicall, &message);
char * addr = malloc(sizeof(char*) * (strlen(destcall) + strlen(digicall) + 2));
sprintf(addr, "%s %s", destcall, digicall);
dlen = ax25_aton(addr, &dest);
free(addr);
// Send a datagram packet to socket
if (sendto(sock, message, strlen(message), 0, (struct sockaddr *)&dest, dlen) == -1) {
result = 1;
}
result = 0;
return PyLong_FromLong(result);
}
static PyObject * datagram_tx(PyObject* self, PyObject* args) {
struct full_sockaddr_ax25 dest;
char *destcall = NULL;
char *message;
int dlen, sock, result;
PyArg_ParseTuple(args, "iss", &sock, &destcall, &message);
dlen = ax25_aton(destcall, &dest);
// Send a datagram packet to socket
if (sendto(sock, message, strlen(message), 0, (struct sockaddr *)&dest, dlen) == -1) {
result = 1;
}
result = 0;
return PyLong_FromLong(result);
}
// Using PF_PACKET Socket
static PyObject * packet_socket(PyObject* self, PyObject* args) {
int fileDescriptor;
fileDescriptor = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_AX25));
return PyLong_FromLong(fileDescriptor);
}
// Close a socket
static PyObject * close_socket(PyObject* self, PyObject* args) {
int fileDescriptor;
int result;
PyArg_ParseTuple(args, "i", &fileDescriptor);
result = close(fileDescriptor);
return PyLong_FromLong(result);
}
static PyObject * packet_tx(PyObject* self, PyObject* args) {
int fileDescriptor;
int result;
int length;
char *buffer;
char *destination;
struct sockaddr socketAddress;
int addressSize = sizeof(socketAddress);
unsigned char newBuffer[1000];
int bufferLength;
int i;
int k;
unsigned char charBuffer;
PyArg_ParseTuple(args, "isis", &fileDescriptor, &buffer, &length, &destination);
bufferLength = strlen(buffer);
i = 0;
k = 0;
while ( i < bufferLength ) {
charBuffer = (buffer[i++] & 0x0f) << 4;
charBuffer = charBuffer | (buffer[i++] & 0x0f);
newBuffer[k++] = charBuffer;
}
strcpy(socketAddress.sa_data, destination);
socketAddress.sa_family = AF_AX25;
result = sendto(fileDescriptor, newBuffer, k, 0, &socketAddress, addressSize);
return Py_BuildValue("i", result);
}
static PyObject * packet_rx(PyObject* self, PyObject* args) {
int fileDescriptor;
int result;
int addressSize;
int packetSize;
int timeout;
struct sockaddr socketAddress;
struct pollfd pollFileDescriptor;
unsigned char receiveBuffer[1024];
PyArg_ParseTuple(args, "ii", &fileDescriptor, &timeout);
// Poll the socket for an available data
pollFileDescriptor.fd = fileDescriptor;
pollFileDescriptor.events = POLLRDNORM;
result = poll(&pollFileDescriptor, 1, timeout);
// Read all packet received
packetSize = 0;
socketAddress.sa_family = AF_UNSPEC;
strcpy(socketAddress.sa_data, "");
if (result == 1) {
addressSize = sizeof(socketAddress);
packetSize = recvfrom(fileDescriptor, receiveBuffer, sizeof(receiveBuffer),
0, &socketAddress, (socklen_t*)&addressSize);
}
return Py_BuildValue("(is)y#", socketAddress.sa_family, socketAddress.sa_data,
receiveBuffer, packetSize);
}
static PyObject *PythonAx25Error;
//////////////////////////////////////////
// Define methods
//////////////////////////////////////////
static PyMethodDef python_ax25_functions[] = {
{"config_load_ports", config_load_ports, METH_VARARGS, ""},
{"config_get_first_port", config_get_first_port, METH_VARARGS, ""},
{"config_get_next_port", config_get_next_port, METH_VARARGS, ""},
{"config_get_port_name", config_get_port_name, METH_VARARGS, ""},
{"config_get_address", config_get_address, METH_VARARGS, ""},
{"config_get_device", config_get_device, METH_VARARGS, ""},
{"config_get_window", config_get_window, METH_VARARGS, ""},
{"config_get_packet_length", config_get_packet_length, METH_VARARGS, ""},
{"config_get_baudrate", config_get_baudrate, METH_VARARGS, ""},
{"config_get_description", config_get_description, METH_VARARGS, ""},
{"network_to_ascii", ntoa, METH_VARARGS, ""},
{"ascii_to_network", aton_entry, METH_VARARGS, ""},
{"datagram_socket", datagram_socket, METH_VARARGS, ""},
{"datagram_bind", datagram_bind, METH_VARARGS, ""},
{"datagram_tx_digi", datagram_tx_digi, METH_VARARGS, ""},
{"datagram_tx", datagram_tx, METH_VARARGS, ""},
{"packet_socket", packet_socket, METH_VARARGS, ""},
{"packet_rx", packet_rx, METH_VARARGS, ""},
{"packet_tx", packet_tx, METH_VARARGS, ""},
{"close_socket", close_socket, METH_VARARGS, ""},
{NULL, NULL, 0, NULL}
};
// Initialize module
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"pythonax25",
"This is a python module for ax.25",
-1,
python_ax25_functions,
NULL,
NULL,
NULL,
NULL,
};
PyMODINIT_FUNC
PyInit_pythonax25(void) {
PyObject * m;
m = PyModule_Create(&moduledef);
if (m == NULL)
return NULL;
PythonAx25Error = PyErr_NewException("pythonax25.error", NULL, NULL);
Py_INCREF(PythonAx25Error);
PyModule_AddObject(m, "error", PythonAx25Error);
return m;
}

10
python-ax25/setup.py Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/python3
from distutils.core import setup, Extension
module1 = Extension('pythonax25', libraries = ['ax25', 'ax25io'], sources = ['pythonax25module.c'])
setup (name = 'pythonax25',
version = '1.0',
description = 'CPython extension for LINUX ax.25 stack',
ext_modules = [module1])